Browse Source

Merge pull request #370 from visuddhinanda:master

collect 查询 加 article_list
Bhikkhu-Kosalla 5 years ago
parent
commit
87dabbbba5
100 changed files with 13787 additions and 3508 deletions
  1. 0 1
      .debug/info.php
  2. 4 1
      .gitignore
  3. 1 0
      .vscode/launch.json
  4. 2388 2388
      app/admin/cs6_para.csv
  5. 3 6
      app/admin/db_update_cs6_para.php
  6. 3 4
      app/admin/update_user_active_time.php
  7. 7 6
      app/admin/word_index_weight_refresh.php
  8. 2 2
      app/article/add_article_to_collect.php
  9. 105 105
      app/article/add_to_collect_dlg.js
  10. 49 6
      app/article/article.js
  11. 32 0
      app/article/card.php
  12. 13 3
      app/article/collect_get.php
  13. 3 3
      app/article/collect_list.php
  14. 177 0
      app/article/function.php
  15. 26 1
      app/article/get.php
  16. 68 40
      app/article/index.php
  17. 2 2
      app/article/list_new.php
  18. 18 0
      app/article/mobile.css
  19. 98 35
      app/article/my_article.js
  20. 9 5
      app/article/my_article_edit.php
  21. 3 1
      app/article/my_article_index.php
  22. 20 10
      app/article/my_article_post.php
  23. 20 3
      app/article/my_article_put.php
  24. 221 212
      app/article/my_collect.js
  25. 5 2
      app/article/my_collect_edit.php
  26. 2 1
      app/article/my_collect_index.php
  27. 35 7
      app/article/my_collect_post.php
  28. 16 2
      app/article/my_collect_put.php
  29. 4 2
      app/article/style.css
  30. 50 32
      app/calendar/index.html
  31. 34 0
      app/channal/card.php
  32. 68 17
      app/channal/channal.js
  33. 86 10
      app/channal/function.php
  34. 27 21
      app/channal/get.php
  35. 3 0
      app/channal/my_channal_index.php
  36. 13 20
      app/channal/my_channal_post.php
  37. 2 1
      app/channal/my_channal_put.php
  38. 34 0
      app/collect/card.php
  39. 197 3
      app/collect/function.php
  40. 2 2
      app/collect/list.php
  41. 61 0
      app/commit/commit.css
  42. 356 0
      app/commit/commit.js
  43. 145 0
      app/commit/commit.php
  44. 16 0
      app/db/table.php
  45. 1 0
      app/dict/comp_csv.php
  46. 0 1
      app/dict/css/style.css
  47. 1 12
      app/dict/css/style_mobile.css
  48. 1 1
      app/dict/dict.js
  49. 79 19
      app/dict/dict_lookup.php
  50. 3 3
      app/dict/p_ending.php
  51. 1 0
      app/dict/redis_comp_part.php
  52. 9 9
      app/dict/redis_refresh_first_mean.php
  53. 33 0
      app/doc/card.php
  54. 1 1
      app/doc/coop.php
  55. 20 0
      app/doc/function.php
  56. 3 2
      app/doc/pcs2db.php
  57. 27 0
      app/group/function.php
  58. 8 3
      app/group/get.php
  59. 78 22
      app/group/group.js
  60. 2 2
      app/group/group_del.php
  61. 2 1
      app/group/index.php
  62. 12 2
      app/group/list.php
  63. 3 3
      app/group/member_del.php
  64. 13 10
      app/group/my_group_put.php
  65. 1 1
      app/guide/guide.css
  66. 5 1
      app/guide/guide.js
  67. 1 1
      app/install/db_insert_sentence.php
  68. 18 8
      app/install/step5.php
  69. 1 1
      app/lang/lang.php
  70. 12 10
      app/pali_sent/redis_upgrade_pali_sent.php
  71. 93 0
      app/pali_text/function.php
  72. 10 6
      app/palicanon/palicanon.js
  73. 20 5
      app/palicanon/style.css
  74. 1 0
      app/path.php
  75. 67 66
      app/pcdl/css/basic_style.css
  76. 44 41
      app/pcdl/css/color_day.css
  77. 220 247
      app/pcdl/css/font.css
  78. 28 12
      app/pcdl/css/style.css
  79. 1 1
      app/pcdl/head_bar.php
  80. 7 0
      app/pcdl/html_head.php
  81. 58 63
      app/pcdl/index.js
  82. 14 0
      app/public/images/svg.md
  83. 235 0
      app/public/js/Highstock-8.0.0/code/css/annotations/popup.css
  84. 239 0
      app/public/js/Highstock-8.0.0/code/css/annotations/popup.scss
  85. 926 0
      app/public/js/Highstock-8.0.0/code/css/highcharts.css
  86. 818 0
      app/public/js/Highstock-8.0.0/code/css/highcharts.scss
  87. 265 0
      app/public/js/Highstock-8.0.0/code/css/stocktools/gui.css
  88. 265 0
      app/public/js/Highstock-8.0.0/code/css/stocktools/gui.scss
  89. 987 0
      app/public/js/Highstock-8.0.0/code/css/themes/dark-unica.css
  90. 122 0
      app/public/js/Highstock-8.0.0/code/css/themes/dark-unica.scss
  91. 940 0
      app/public/js/Highstock-8.0.0/code/css/themes/grid-light.css
  92. 33 0
      app/public/js/Highstock-8.0.0/code/css/themes/grid-light.scss
  93. 956 0
      app/public/js/Highstock-8.0.0/code/css/themes/sand-signika.css
  94. 74 0
      app/public/js/Highstock-8.0.0/code/css/themes/sand-signika.scss
  95. 141 0
      app/public/js/Highstock-8.0.0/code/es-modules/annotations/ControlPoint.js
  96. 453 0
      app/public/js/Highstock-8.0.0/code/es-modules/annotations/MockPoint.js
  97. 1321 0
      app/public/js/Highstock-8.0.0/code/es-modules/annotations/annotations.src.js
  98. 84 0
      app/public/js/Highstock-8.0.0/code/es-modules/annotations/controllable/ControllableCircle.js
  99. 93 0
      app/public/js/Highstock-8.0.0/code/es-modules/annotations/controllable/ControllableImage.js
  100. 509 0
      app/public/js/Highstock-8.0.0/code/es-modules/annotations/controllable/ControllableLabel.js

+ 0 - 1
.debug/info.php

@@ -1 +0,0 @@
-<?php phpinfo(); ?>

+ 4 - 1
.gitignore

@@ -1,6 +1,9 @@
+/.debug
+/.vscode
 /tmp
 /tmp
 /.vscode
 /.vscode
 .DS_Store
 .DS_Store
 /.debug
 /.debug
 .favorites.json
 .favorites.json
-/vendor
+/vendor
+/node_modules

+ 1 - 0
.vscode/launch.json

@@ -4,6 +4,7 @@
   // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
   // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
   "version": "0.2.0",
   "version": "0.2.0",
   "configurations": [
   "configurations": [
+  
     {
     {
       "type": "chrome",
       "type": "chrome",
       "request": "launch",
       "request": "launch",

File diff suppressed because it is too large
+ 2388 - 2388
app/admin/cs6_para.csv


+ 3 - 6
app/admin/db_update_cs6_para.php

@@ -1,16 +1,13 @@
 <?php
 <?php
+#将cscd4 段落编号插入数据库 用于义注跳转
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once "../path.php";
 require_once "../path.php";
 
 
-$db_file = _FILE_DB_PALITEXT_;
-PDO_Connect("$db_file");
-
+PDO_Connect(_FILE_DB_PALITEXT_);
 $query = "SELECT * from books where 1";
 $query = "SELECT * from books where 1";
 $books = PDO_FetchAll($query);
 $books = PDO_FetchAll($query);
 
 
-$db_file = _FILE_DB_PAGE_INDEX_;
-PDO_Connect("$db_file");
-
+PDO_Connect(_FILE_DB_PAGE_INDEX_);
 // 打开文件并读取数据
 // 打开文件并读取数据
 $irow = 0;
 $irow = 0;
 if (($fp = fopen("./cs6_para.csv", "r")) !== false) {
 if (($fp = fopen("./cs6_para.csv", "r")) !== false) {

+ 3 - 4
app/admin/update_user_active_time.php

@@ -1,4 +1,5 @@
 <?php
 <?php
+#已经废弃
 require_once '../path.php';
 require_once '../path.php';
 
 
 date_default_timezone_set("UTC");
 date_default_timezone_set("UTC");
@@ -8,12 +9,10 @@ $start = strtotime($last . " +1 day");
 $end = strtotime($last . " +2 day");
 $end = strtotime($last . " +2 day");
 $today = strtotime("today");
 $today = strtotime("today");
 
 
-$dns = "" . _FILE_DB_USER_ACTIVE_;
-$dbh = new PDO($dns, "", "", array(PDO::ATTR_PERSISTENT => true));
+$dbh = new PDO(_FILE_DB_USER_ACTIVE_, "", "", array(PDO::ATTR_PERSISTENT => true));
 $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 
 
-$dns = "" . _FILE_DB_USER_ACTIVE_INDEX_;
-$dbh_index = new PDO($dns, "", "", array(PDO::ATTR_PERSISTENT => true));
+$dbh_index = new PDO(_FILE_DB_USER_ACTIVE_INDEX_, "", "", array(PDO::ATTR_PERSISTENT => true));
 $dbh_index->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 $dbh_index->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 /* 开始一个事务,关闭自动提交 */
 /* 开始一个事务,关闭自动提交 */
 $dbh_index->beginTransaction();
 $dbh_index->beginTransaction();

+ 7 - 6
app/admin/word_index_weight_refresh.php

@@ -6,8 +6,8 @@ require_once '../path.php';
 require_once './word_index_weight_table.php';
 require_once './word_index_weight_table.php';
 
 
 if (isset($_GET["from"])) {
 if (isset($_GET["from"])) {
-    $from = $_GET["from"];
-    $to = $_GET["to"];
+    $from = (int)$_GET["from"];
+    $to = (int)$_GET["to"];
 } else {
 } else {
     if ($argc != 3) {
     if ($argc != 3) {
         echo "无效的参数 ";
         echo "无效的参数 ";
@@ -20,18 +20,19 @@ if (isset($_GET["from"])) {
     }
     }
 }
 }
 
 
-$dh_word = new PDO("" . _FILE_DB_WORD_INDEX_, "", "");
+$dh_word = new PDO( _FILE_DB_WORD_INDEX_, "", "");
 $dh_word->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 $dh_word->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 
 
-$dh_pali = new PDO("" . _FILE_DB_PALI_INDEX_, "", "");
+$dh_pali = new PDO( _FILE_DB_PALI_INDEX_, "", "");
 $dh_pali->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 $dh_pali->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 
 
 echo "from=$from to = $to \n";
 echo "from=$from to = $to \n";
 for ($i = $from; $i <= $to; $i++) {
 for ($i = $from; $i <= $to; $i++) {
     $time_start = microtime(true);
     $time_start = microtime(true);
     echo "正在处理 book= $i ";
     echo "正在处理 book= $i ";
-    $query = "SELECT max(paragraph) from word where book={$i}";
-    $stmt = $dh_pali->query($query);
+    $query = "SELECT max(paragraph) from word where book=?";
+	$stmt = $dh_pali->prepare($query);
+    $stmt->execute(array($i));
     $row = $stmt->fetch(PDO::FETCH_NUM);
     $row = $stmt->fetch(PDO::FETCH_NUM);
     if ($row) {
     if ($row) {
         $max_para = $row[0];
         $max_para = $row[0];

+ 2 - 2
app/article/add_article_to_collect.php

@@ -1,5 +1,5 @@
 <?php
 <?php
-//查询term字典
+//添加文章到文集
 
 
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
@@ -16,7 +16,7 @@ if(isset($_POST["id"])){
     $dirty_collect = array();
     $dirty_collect = array();
     $data = json_decode($_POST["data"]);
     $data = json_decode($_POST["data"]);
     $title = $_POST["title"];
     $title = $_POST["title"];
-    PDO_Connect(""._FILE_DB_USER_ARTICLE_);
+    PDO_Connect(_FILE_DB_USER_ARTICLE_);
     $article_id=$_POST["id"];
     $article_id=$_POST["id"];
     //找出脏的collect
     //找出脏的collect
     $query = "SELECT collect_id FROM article_list  WHERE article_id = ? ";
     $query = "SELECT collect_id FROM article_list  WHERE article_id = ? ";

+ 105 - 105
app/article/add_to_collect_dlg.js

@@ -1,120 +1,120 @@
 function add_to_collect_dlg_init() {
 function add_to_collect_dlg_init() {
-  $("[vui='collect-dlg']").each(function () {
-    $(this).css("position", "relative");
-    let html = "";
-    html +=
-      "<button class='button_add_to_collect icon_btn' title='" +
-      gLocal.gui.add_to +
-      gLocal.gui.anthology +
-      "'>";
-    html +=
-      "<svg class='icon'><use xlink:href='../studio/svg/icon.svg#add_to_anthology'></use></svg></button><div class='float_dlg' style='left:0;'></div>";
+	$("[vui='collect-dlg']").each(function () {
+		$(this).css("position", "relative");
+		let html = "";
+		html += '<span class="icon_btn_div">';
+		html += '<span class="icon_btn_tip">' + gLocal.gui.add_to + gLocal.gui.anthology + "</span>";
+		html += "<button class='button_add_to_collect icon_btn' >";
+		html += "<svg class='icon'>";
+		html += "<use xlink:href='../studio/svg/icon.svg#add_to_anthology'></use>";
+		html += "</svg>";
+		html += "</button>";
+		html += "<span class='float_dlg' style='top: 20px;' ></span>";
+		html += "</span>";
+		$(this).html(html);
+	});
 
 
-    $(this).html(html);
-  });
+	$(".button_add_to_collect").click(function () {
+		let html = "";
+		let article_id = $(this).parent().attr("a_id");
+		html += "<div id='add_to_collect_dlg_" + article_id + "'>";
+		html += "<div >";
+		html += "<input type='input'  placeholder='" + gLocal.gui.search + "' />";
+		html += "</div>";
+		html += "<div>";
+		html += "<div class='exist'>";
+		html += "</div>";
+		html += "<div class='others'>";
+		html += "</div>";
+		html += "<div id='add_to_collection_msg'>";
+		html += "<a href='../article/my_collect_index.php' target='_blank'>添加新的文集</a>";
+		html += "</div>";
+		html += "</div>";
+		html += "<div style='display:flex;justify-content: space-between;'>";
+		html +=
+			"<button onclick=\"article_add_to_collect_cancel('" +
+			article_id +
+			"')\">" +
+			gLocal.gui.cancel +
+			"</button>";
+		html +=
+			"<button onclick=\"article_add_to_collect_ok('" + article_id + "')\">" + gLocal.gui.finish + "</button>";
+		html += "</div>";
+		html += "</div>";
+		$(this).siblings(".float_dlg").first().html(html);
+		$(this).siblings(".float_dlg").first().show();
+		$.get(
+			"../article/list_article_in_collect.php",
+			{
+				id: article_id,
+			},
+			function (data, status) {
+				let collect_list = JSON.parse(data);
 
 
-  $(".button_add_to_collect").click(function () {
-    let html = "";
-    let article_id = $(this).parent().attr("a_id");
-    html += "<div id='add_to_collect_dlg_" + article_id + "'>";
-    html += "<div >";
-    html += "<input type='input'  placeholder='" + gLocal.gui.search + "' />";
-    html += "</div>";
-    html += "<div>";
-    html += "<div class='exist'>";
-    html += "</div>";
-    html += "<div class='others'>";
-    html += "</div>";
-    html += "</div>";
-    html += "<div style='display:flex;justify-content: space-evenly;'>";
-    html += "<button onclick='collect_new()'>" + gLocal.gui.new_composition + "</button>";
-    html +=
-      "<button onclick=\"article_add_to_collect_cancel('" +
-      article_id +
-      "')\">" + gLocal.gui.cancel + "</button>";
-    html +=
-      "<button onclick=\"article_add_to_collect_ok('" +
-      article_id +
-      "')\">" + gLocal.gui.finish + "</button>";
-    html += "</div>";
-    html += "</div>";
-    $(this).siblings(".float_dlg").first().html(html);
-    $(this).siblings(".float_dlg").first().show();
-    $.get(
-      "../article/list_article_in_collect.php",
-      {
-        id: article_id,
-      },
-      function (data, status) {
-        let collect_list = JSON.parse(data);
+				let id = collect_list.article_id;
+				let html_exist = "";
 
 
-        let id = collect_list.article_id;
-        let html_exist = "";
-        for (const iterator of collect_list.exist) {
-          html_exist +=
-            "<div><input type='checkbox' class='collect' collect_id='" +
-            iterator.id +
-            "' checked />";
-          html_exist += iterator.title;
-          html_exist += "</div>";
-        }
-        $("#add_to_collect_dlg_" + id)
-          .find(".exist")
-          .first()
-          .html(html_exist);
+				for (const iterator of collect_list.exist) {
+					html_exist +=
+						"<div><input type='checkbox' class='collect' collect_id='" + iterator.id + "' checked />";
+					html_exist += iterator.title;
+					html_exist += "</div>";
+				}
+				$("#add_to_collect_dlg_" + id)
+					.find(".exist")
+					.first()
+					.html(html_exist);
 
 
-        if (collect_list.others) {
-          let html_others = "";
-          for (const iterator of collect_list.others) {
-            html_others +=
-              "<div><input type='checkbox' class='collect' collect_id='" +
-              iterator.id +
-              "' />";
-            html_others += iterator.title;
-            html_others += "</div>";
-          }
-          $("#add_to_collect_dlg_" + id)
-            .find(".others")
-            .first()
-            .html(html_others);
-        }
-      }
-    );
-  });
+				if (collect_list.others) {
+					let html_others = "";
+					for (const iterator of collect_list.others) {
+						html_others +=
+							"<div><input type='checkbox' class='collect' collect_id='" + iterator.id + "' />";
+						html_others += iterator.title;
+						html_others += "</div>";
+					}
+					$("#add_to_collect_dlg_" + id)
+						.find(".others")
+						.first()
+						.html(html_others);
+				}
+			}
+		);
+	});
 }
 }
 
 
 function article_add_to_collect_ok(article_id) {
 function article_add_to_collect_ok(article_id) {
-  let obj = document.querySelectorAll(".collect");
-  let collect_id = new Array();
-  for (const iterator of obj) {
-    if (iterator.checked == true) {
-      collect_id.push(iterator.getAttributeNode("collect_id").value);
-    }
-  }
-  $.post(
-    "../article/add_article_to_collect.php",
-    {
-      id: article_id,
-      title: $("#input_article_title").val(),
-      data: JSON.stringify(collect_id),
-    },
-    function (data) {
-      let result = JSON.parse(data);
-      if (result.status > 0) {
-        alert(result.message);
-      } else {
-        add_to_collect_dlg_close(result.id);
-      }
-    }
-  );
+	let obj = document.querySelectorAll(".collect");
+	let collect_id = new Array();
+	for (const iterator of obj) {
+		if (iterator.checked == true) {
+			collect_id.push(iterator.getAttributeNode("collect_id").value);
+		}
+	}
+	$.post(
+		"../article/add_article_to_collect.php",
+		{
+			id: article_id,
+			title: $("#input_article_title").val(),
+			data: JSON.stringify(collect_id),
+		},
+		function (data) {
+			let result = JSON.parse(data);
+			if (result.status > 0) {
+				alert(result.message);
+			} else {
+				add_to_collect_dlg_close(result.id);
+			}
+		}
+	);
 }
 }
 
 
 function article_add_to_collect_cancel(article_id) {
 function article_add_to_collect_cancel(article_id) {
-  add_to_collect_dlg_close(article_id);
+	add_to_collect_dlg_close(article_id);
 }
 }
 
 
 function add_to_collect_dlg_close(article_id) {
 function add_to_collect_dlg_close(article_id) {
-  $("#add_to_collect_dlg_" + article_id)
-    .parent()
-    .hide();
+	$("#add_to_collect_dlg_" + article_id)
+		.parent()
+		.hide();
 }
 }

+ 49 - 6
app/article/article.js

@@ -4,11 +4,12 @@ var _lang = "";
 var _author = "";
 var _author = "";
 var _display = "";
 var _display = "";
 var _collect_id = "";
 var _collect_id = "";
+var _collection_id = "";
 
 
 function article_onload() {
 function article_onload() {
 	historay_init();
 	historay_init();
 }
 }
-function articel_load(id) {
+function articel_load(id, collection_id) {
 	if (id == "") {
 	if (id == "") {
 		return;
 		return;
 	}
 	}
@@ -16,6 +17,7 @@ function articel_load(id) {
 		"../article/get.php",
 		"../article/get.php",
 		{
 		{
 			id: id,
 			id: id,
+			collection_id: collection_id,
 			setting: "",
 			setting: "",
 		},
 		},
 		function (data, status) {
 		function (data, status) {
@@ -29,6 +31,7 @@ function articel_load(id) {
 						$("#article_author").html(result.username.nickname + "@" + result.username.username);
 						$("#article_author").html(result.username.nickname + "@" + result.username.username);
 						$("#contents").html(note_init(result.content));
 						$("#contents").html(note_init(result.content));
 						note_refresh_new();
 						note_refresh_new();
+						guide_init();
 					}
 					}
 				} catch (e) {
 				} catch (e) {
 					console.error(e);
 					console.error(e);
@@ -64,7 +67,7 @@ function collect_load(id) {
 						$("#contents").html(marked(result.summary));
 						$("#contents").html(marked(result.summary));
 
 
 						let article_list = JSON.parse(result.article_list);
 						let article_list = JSON.parse(result.article_list);
-						render_article_list(article_list);
+						render_article_list(article_list, id);
 					}
 					}
 				} catch (e) {
 				} catch (e) {
 					console.error(e);
 					console.error(e);
@@ -93,7 +96,7 @@ function articel_load_collect(article_id) {
 						$("#pali_pedia").html(strTitle);
 						$("#pali_pedia").html(strTitle);
 
 
 						let article_list = JSON.parse(result[0].article_list);
 						let article_list = JSON.parse(result[0].article_list);
-						render_article_list(article_list);
+						render_article_list(article_list, result[0].id);
 					}
 					}
 				} catch (e) {
 				} catch (e) {
 					console.error(e);
 					console.error(e);
@@ -105,7 +108,8 @@ function articel_load_collect(article_id) {
 	);
 	);
 }
 }
 
 
-function render_article_list(article_list) {
+//在collect 中 的article列表
+function render_article_list(article_list, collection_id) {
 	let html = "";
 	let html = "";
 	html += "<ul>";
 	html += "<ul>";
 	let display = "";
 	let display = "";
@@ -114,16 +118,31 @@ function render_article_list(article_list) {
 	}
 	}
 	let prevArticle = "无";
 	let prevArticle = "无";
 	let nextArticle = "无";
 	let nextArticle = "无";
+	let urlCollection = "&collection=" + collection_id;
 	for (let index = 0; index < article_list.length; index++) {
 	for (let index = 0; index < article_list.length; index++) {
 		const element = article_list[index];
 		const element = article_list[index];
 		if (element.article == _articel_id) {
 		if (element.article == _articel_id) {
 			if (index > 0) {
 			if (index > 0) {
 				const prev = article_list[index - 1];
 				const prev = article_list[index - 1];
-				prevArticle = "<a href='../article/index.php?id=" + prev.article + display + "'>" + prev.title + "</a>";
+				prevArticle =
+					"<a href='../article/index.php?id=" +
+					prev.article +
+					display +
+					urlCollection +
+					"'>" +
+					prev.title +
+					"</a>";
 			}
 			}
 			if (index < article_list.length - 1) {
 			if (index < article_list.length - 1) {
 				const next = article_list[index + 1];
 				const next = article_list[index + 1];
-				nextArticle = "<a href='../article/index.php?id=" + next.article + display + "'>" + next.title + "</a>";
+				nextArticle =
+					"<a href='../article/index.php?id=" +
+					next.article +
+					display +
+					urlCollection +
+					"'>" +
+					next.title +
+					"</a>";
 			}
 			}
 			$("#contents_nav_left").html(prevArticle);
 			$("#contents_nav_left").html(prevArticle);
 			$("#contents_nav_right").html(nextArticle);
 			$("#contents_nav_right").html(nextArticle);
@@ -135,6 +154,7 @@ function render_article_list(article_list) {
 			"<a href='../article/index.php?id=" +
 			"<a href='../article/index.php?id=" +
 			element.article +
 			element.article +
 			display +
 			display +
+			urlCollection +
 			"'>" +
 			"'>" +
 			element.title +
 			element.title +
 			"</a></li>";
 			"</a></li>";
@@ -153,5 +173,28 @@ function set_channal(channalid) {
 	if (_display != "") {
 	if (_display != "") {
 		url += "&display=" + _display;
 		url += "&display=" + _display;
 	}
 	}
+	if (_mode != "") {
+		url += "&mode=" + _mode;
+	}
+	if (_direction != "") {
+		url += "&direction=" + _direction;
+	}
+	location.assign(url);
+}
+function setMode(mode = "read") {
+	let url = "../article/index.php?id=" + _articel_id;
+	if (_channal != "") {
+		url += "&channal=" + _channal;
+	}
+	if (_display != "") {
+		if (mode == "read") {
+			url += "&display=" + _display;
+		} else {
+			url += "&display=sent";
+		}
+	}
+	if (mode != "") {
+		url += "&mode=" + mode;
+	}
 	location.assign(url);
 	location.assign(url);
 }
 }

+ 32 - 0
app/article/card.php

@@ -0,0 +1,32 @@
+<?php
+//
+
+require_once "../path.php";
+require_once '../redis/function.php';
+require_once "../article/function.php";
+require_once '../ucenter/function.php';
+
+
+if (isset($_GET["id"])) {
+	$output["id"]=$_GET["id"];
+	$redis = redis_connect();
+	$article = new Article($redis); 
+	$result = $article->getInfo($_GET["id"]);
+	if ($result) {
+		$_userinfo = new UserInfo();
+		$name = $_userinfo->getName($result["owner"]);
+		$strData .= "<div>标题:".$result["title"]."</div>";
+		$strData .=  "<div>创建人:".$name["nickname"]."</div>";
+		$strData .=  "<div>创建时间:".date("Y/m/d",$result["create_time"]/1000)."</div>";
+		$strData .=  "<div>简介:".$result["summary"]."</div>";
+	} else {
+		$strData .=  "unkow";
+	}
+	$output["data"] = $strData;
+} else {
+	$output["id"]=0;
+	$output["data"] = "unkow";
+}
+echo json_encode($output, JSON_UNESCAPED_UNICODE);
+
+?>

+ 13 - 3
app/article/collect_get.php

@@ -1,14 +1,24 @@
 <?php
 <?php
-//查询term字典
+#获取文集信息
 
 
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
 require_once '../ucenter/function.php';
 require_once '../ucenter/function.php';
+require_once '../collect/function.php';
+require_once '../redis/function.php';
 
 
-
+$redis = redis_connect();
 if(isset($_GET["id"])){
 if(isset($_GET["id"])){
-    PDO_Connect(""._FILE_DB_USER_ARTICLE_);
+	//查询权限
+
+	$collection = new CollectInfo($redis); 
+	$power = $collection->getPower($_GET["id"]);
+	if($power<10){
+		echo json_encode(array(), JSON_UNESCAPED_UNICODE);
+        exit;
+	}
+    PDO_Connect(_FILE_DB_USER_ARTICLE_);
     $id=$_GET["id"];
     $id=$_GET["id"];
     $query = "select * from collect  where id = ? ";
     $query = "select * from collect  where id = ? ";
     $Fetch = PDO_FetchRow($query,array($id));
     $Fetch = PDO_FetchRow($query,array($id));

+ 3 - 3
app/article/collect_list.php

@@ -1,5 +1,5 @@
 <?php
 <?php
-//查询term字典
+#某用户的文章列表
 
 
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
@@ -10,8 +10,8 @@ require_once '../ucenter/function.php';
 if(isset($_GET["userid"])){
 if(isset($_GET["userid"])){
     PDO_Connect(""._FILE_DB_USER_ARTICLE_);
     PDO_Connect(""._FILE_DB_USER_ARTICLE_);
     $userid=$_GET["userid"];
     $userid=$_GET["userid"];
-    $query = "SELECT * from collect  where owner = ".$PDO->quote($userid)." and status <> 0 order by modify_time DESC";
-    $Fetch = PDO_FetchAll($query);
+    $query = "SELECT * from collect  where owner = ? and status <> 0 order by modify_time DESC";
+    $Fetch = PDO_FetchAll($query,array($userid));
     echo json_encode($Fetch, JSON_UNESCAPED_UNICODE);
     echo json_encode($Fetch, JSON_UNESCAPED_UNICODE);
     exit;
     exit;
 }
 }

+ 177 - 0
app/article/function.php

@@ -0,0 +1,177 @@
+<?php
+require_once "../path.php";
+require_once "../share/function.php";
+require_once "../db/table.php";
+
+class Article extends Table
+{
+    function __construct($redis=false) {
+		parent::__construct(_FILE_DB_USER_ARTICLE_, "article", "", "",$redis);
+    }
+
+    public function getInfo($id){
+		$output = array();
+		if($this->redis!==false){
+			if($this->redis->exists("article://".$id)===1){
+				$output["id"]=$this->redis->hGet("article://".$id,"id");
+				$output["title"]=$this->redis->hGet("article://".$id,"title");
+				$output["subtitle"]=$this->redis->hGet("article://".$id,"subtitle");
+				$output["owner"]=$this->redis->hGet("article://".$id,"owner");
+				$output["summary"]=$this->redis->hGet("article://".$id,"summary");
+				$output["tag"]=$this->redis->hGet("article://".$id,"tag");
+				$output["status"]=$this->redis->hGet("article://".$id,"status");
+				$output["create_time"]=$this->redis->hGet("article://".$id,"create_time");
+				$output["modify_time"]=$this->redis->hGet("article://".$id,"modify_time");
+				return $output;
+			}
+		}
+        $query = "SELECT id,title,owner,summary,tag,status,create_time,modify_time FROM article WHERE id= ? ";
+        $stmt = $this->dbh->prepare($query);
+        $stmt->execute(array($id));
+        $output = $stmt->fetch(PDO::FETCH_ASSOC);
+        if($output){
+			if($this->redis!==false){
+				$this->redis->hSet("article://".$id,"id",$output["id"]);
+				$this->redis->hSet("article://".$id,"title",$output["title"]);
+				$this->redis->hSet("article://".$id,"subtitle",$output["subtitle"]);
+				$this->redis->hSet("article://".$id,"summary",$output["summary"]);
+				$this->redis->hSet("article://".$id,"owner",$output["owner"]);
+				$this->redis->hSet("article://".$id,"tag",$output["tag"]);
+				$this->redis->hSet("article://".$id,"status",$output["status"]);
+				$this->redis->hSet("article://".$id,"create_time",$output["create_time"]);
+				$this->redis->hSet("article://".$id,"modify_time",$output["modify_time"]);
+			}
+            return $output;
+        }
+        else{
+            return false;
+        }
+	}
+
+    public function getContent($id){
+		$output = array();
+		if($this->redis!==false){
+			if($this->redis->hExists("article://".$id,"content")===TRUE){
+				$content=$this->redis->hGet("article://".$id,"content");
+				return $content;
+			}
+		}
+        $query = "SELECT content FROM article WHERE id= ? ";
+        $stmt = $this->dbh->prepare($query);
+        $stmt->execute(array($id));
+        $output = $stmt->fetch(PDO::FETCH_ASSOC);
+        if($output){
+			if($this->redis!==false){
+				$this->redis->hSet("article://".$id,"content",$output["content"]);
+			}
+            return $output["content"];
+        }
+        else{
+            return false;
+        }
+	}
+
+	public function getPower($id,$collectionId=""){
+		#查询用户对此是否有权限	
+		if(isset($_COOKIE["userid"])){
+			$userId = $_COOKIE["userid"];
+		}
+		else{
+			$userId=0;
+		}
+		if($this->redis!==false){
+			$power = $this->redis->hGet("power://article/".$id,$userId);
+			if($power!==FALSE){
+				return $power;
+			}
+		}
+		$iPower = 0;
+		$query = "SELECT owner,status FROM article WHERE id=?  ";
+		$stmt = $this->dbh->prepare($query);
+		$stmt->execute(array($id));
+		$channel = $stmt->fetch(PDO::FETCH_ASSOC);
+		if($channel){
+			if(!isset($_COOKIE["userid"])){
+				#未登录用户
+				if($channel["status"]==30){
+					#全网公开有读取和建议权限
+					return 10;
+				}
+				else{
+					#其他状态没有任何权限
+					return 0;
+				}
+			}
+			else{
+				if($channel["owner"]==$_COOKIE["userid"]){
+					#自己的
+					return 30;
+				}
+				else if($channel["status"]>=30){
+					#全网公开的 可以提交pr
+					$iPower = 10;
+				}				
+			}
+		}
+		#查询共享权限,如果共享权限更大,覆盖上面的的
+		$sharePower = share_get_res_power($_COOKIE["userid"],$id);
+		if($collectionId!=""){
+			$sharePowerCollection = share_get_res_power($_COOKIE["userid"],$collectionId);
+		}
+		else{
+			$sharePowerCollection =0;
+		}
+		if($sharePower>$iPower){
+			$iPower=$sharePower;
+		}
+		if($sharePowerCollection>$iPower){
+			$iPower=$sharePowerCollection;
+		}
+		$this->redis->hSet("power://article/".$id,$_COOKIE["userid"],$iPower);
+		return $iPower;
+	}
+
+}
+
+
+class ArticleList extends Table
+{
+    function __construct($redis=false) {
+		parent::__construct(_FILE_DB_USER_ARTICLE_, "article_list", "", "",$redis);
+    }
+
+	function upgrade($collectionId,$articleList=array()){
+		if(count($articleList)==0){
+			return false;
+		}
+		# 更新 article_list 表
+		$query = "DELETE FROM article_list WHERE collect_id = ? ";
+		$stmt = $this->dbh->prepare($query);
+		if($stmt){
+			$stmt->execute($collectionId);
+		}
+        
+		if(count($articleList)>0){
+			/* 开始一个事务,关闭自动提交 */
+			$this->dbh->beginTransaction();
+			$query = "INSERT INTO article_list (collect_id, article_id,level,title) VALUES ( ?, ?, ? , ? )";
+			$sth = $this->dbh->prepare($query);
+			foreach ($articleList as $row) {
+				$sth->execute(array($_POST["id"],$row->article,$row->level,$row->title));
+				if($redis){
+					#删除article权限缓存
+					$redis->del("power://article/".$row->article);
+				}
+			}
+			$PDO->commit();
+			if (!$sth || ($sth && $sth->errorCode() != 0)) {
+				/*  识别错误且回滚更改  */
+				$PDO->rollBack();
+				$error = PDO_ErrorInfo();
+				$respond['status']=1;
+				$respond['message']=$error[2];
+			}
+		}
+	}
+}
+?>

+ 26 - 1
app/article/get.php

@@ -1,18 +1,43 @@
 <?php
 <?php
-//查询term字典
+//获取article内容
 
 
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
 require_once '../ucenter/function.php';
 require_once '../ucenter/function.php';
+require_once '../redis/function.php';
+require_once "../article/function.php";
+
 
 
 
 
 if(isset($_GET["id"])){
 if(isset($_GET["id"])){
+	//查询权限
+	$collectionId = $_GET["collection_id"];
+	$redis = redis_connect();
+	$article = new Article($redis); 
+	$power = $article->getPower($_GET["id"],$collectionId);
+	if($power<10){
+		$output = array();
+		$output["title"]="Sorry对不起";
+		$output["subtitle"]="No Power For Read<br>没有阅读权限";
+		$output["summary"]="";
+		$output["content"]="This is a **private** rescouce, reading need special <guide gid='power_set'><b>power</b></guide>.<br>该资源是**私密**资源,阅读需要<guide gid='power_set'><b>权限</b></guide>。";
+		$output["owner"]="";
+		$output["username"]=array("username"=>"","nickname"=>"");
+		$output["status"]="";
+		echo json_encode($output, JSON_UNESCAPED_UNICODE);
+		exit;
+	}
+	#查询权限结束
+	/*
     PDO_Connect(""._FILE_DB_USER_ARTICLE_);
     PDO_Connect(""._FILE_DB_USER_ARTICLE_);
     $id=$_GET["id"];
     $id=$_GET["id"];
     $query = "SELECT * FROM article  WHERE id = ? ";
     $query = "SELECT * FROM article  WHERE id = ? ";
     $Fetch = PDO_FetchRow($query,array($id));
     $Fetch = PDO_FetchRow($query,array($id));
+	*/
+	$Fetch = $article->getInfo($_GET["id"]);
     if($Fetch){
     if($Fetch){
+		$Fetch["content"] = $article->getContent($_GET["id"]);
         $userinfo = new UserInfo();
         $userinfo = new UserInfo();
         $user = $userinfo->getName($Fetch["owner"]);
         $user = $userinfo->getName($Fetch["owner"]);
         $Fetch["username"] = $user;
         $Fetch["username"] = $user;

+ 68 - 40
app/article/index.php

@@ -24,9 +24,10 @@ require_once "../pcdl/html_head.php";
 	if(isset($_GET["collect"])){
 	if(isset($_GET["collect"])){
 		echo "_collect_id='".$_GET["collect"]."';";
 		echo "_collect_id='".$_GET["collect"]."';";
 	}
 	}
-	if(isset($_GET["display"])){
-		echo "_display='".$_GET["display"]."';";
+	if(isset($_GET["collection"])){
+		echo "_collection_id='".$_GET["collection"]."';";
 	}
 	}
+
 	if(isset($_GET["channal"])){
 	if(isset($_GET["channal"])){
 		echo "_channal='".$_GET["channal"]."';";
 		echo "_channal='".$_GET["channal"]."';";
 	}
 	}
@@ -36,6 +37,63 @@ require_once "../pcdl/html_head.php";
 	if(isset($_GET["author"])){
 	if(isset($_GET["author"])){
 		echo "_author='".$_GET["author"]."';";
 		echo "_author='".$_GET["author"]."';";
 	}
 	}
+	if(isset($_GET["mode"]) && $_GET["mode"]=="edit"){
+		$_mode = "edit";
+		echo "_mode='edit';";
+	}
+	else{
+		$_mode = "read";
+		echo "_mode='read';";
+	}
+	if(isset($_GET["display"])){
+		if($_mode == "edit"){
+			$_display = "sent";
+			echo "_display='sent';";	
+		}
+		else{
+			$_display = $_GET["display"];
+			echo "_display='".$_GET["display"]."';";			
+		}
+	}
+	else{
+		if($_mode=="read"){
+			$_display = "para";
+			echo "_display='para';";
+		}
+		else{
+			$_display = "sent";
+			echo "_display='sent';";			
+		}
+
+	}	
+	if(isset($_GET["direction"])){
+		$_direction = $_GET["direction"];
+		echo "_direction='".$_GET["direction"]."';";
+	}
+	else{
+		if($_mode=="read"){
+			$_direction = "row";
+			echo "_direction='row';";
+		}
+		else{
+			$_direction = "col";
+			echo "_direction='col';";
+		}
+	}
+	$contentClass= "";
+	if($_direction=="row"){
+		$contentClass .= ' horizontal ';
+	}
+	else{
+		$contentClass .= ' vertical ';
+	}
+	if($_display=="para"){
+		$contentClass .= ' para_mode ';
+	}
+	else{
+		$contentClass .= ' sent_mode ';
+	}
+
 	?>
 	?>
 	</script>
 	</script>
 
 
@@ -53,8 +111,8 @@ require_once "../pcdl/html_head.php";
 		<span><?php echo $_local->gui->anthology; ?></span>
 		<span><?php echo $_local->gui->anthology; ?></span>
 	</div>
 	</div>
 
 
-	<div>
-		<span>
+	<div style="margin: auto 0;">
+		<span id="head_span">
 		<?php
 		<?php
 		
 		
 		if(isset($_GET["id"])){
 		if(isset($_GET["id"])){
@@ -66,38 +124,6 @@ require_once "../pcdl/html_head.php";
 			echo "<a href='../article/frame.php?id=".$_GET["id"];
 			echo "<a href='../article/frame.php?id=".$_GET["id"];
 			echo "'>{$_local->gui->add}{$_local->gui->subfield}</a></button>";	
 			echo "'>{$_local->gui->add}{$_local->gui->subfield}</a></button>";	
 			
 			
-			
-			if(isset($_GET["display"]) && $_GET["display"]=="para"){
-				echo "<button class='icon_btn active' title='{$_local->gui->show} {$_local->gui->each_paragraph}'>";
-				echo $_local->gui->each_paragraph;
-				echo "</button>";
-			}
-			else{
-				
-				echo "<button class='icon_btn'>";
-				echo "<a href='../article/?id=".$_GET["id"];
-				if(isset($_GET["channal"])){
-					echo "&channal=".$_GET["channal"];
-				}
-				echo "&display=para'  title='{$_local->gui->show} {$_local->gui->each_paragraph}'>";		
-				echo $_local->gui->each_paragraph;
-				echo "</a>";
-				echo "</button>";
-			}
-	
-			if(isset($_GET["display"]) && $_GET["display"]=="sent"){
-				echo "<button class='icon_btn active'  title='{$_local->gui->show} {$_local->gui->each_sentence}'>";
-				echo $_local->gui->each_sentence;
-				echo "</button>";
-			}
-			else{
-				echo "<button class='icon_btn'><a href='../article/?id=".$_GET["id"];
-				if(isset($_GET["channal"])){
-					echo "&channal=".$_GET["channal"];
-				}
-				echo "&display=sent";
-				echo "'  title='{$_local->gui->show} {$_local->gui->each_sentence}'>{$_local->gui->each_sentence}</a></button>";
-			}
 		}
 		}
 
 
 		?>
 		?>
@@ -115,7 +141,8 @@ require_once "../pcdl/html_head.php";
 </div>
 </div>
 <div id="contents_view">
 <div id="contents_view">
 	<div id="contents_div">
 	<div id="contents_div">
-		<div id="contents">
+	
+		<div id="contents" class="<?php echo $contentClass;?>">
 		<?php echo $_local->gui->loading; ?>...
 		<?php echo $_local->gui->loading; ?>...
 		</div>
 		</div>
 		<div id="contents_foot">
 		<div id="contents_foot">
@@ -161,7 +188,7 @@ require_once "../pcdl/html_head.php";
 	note_create();
 	note_create();
 	historay_init();
 	historay_init();
 	if(_collect_id==""){
 	if(_collect_id==""){
-		articel_load(_articel_id);
+		articel_load(_articel_id,_collection_id);
 		articel_load_collect(_articel_id);
 		articel_load_collect(_articel_id);
 	}
 	}
 	else{
 	else{
@@ -169,8 +196,6 @@ require_once "../pcdl/html_head.php";
 	}
 	}
 	});
 	});
 
 
-
-
 	 window.addEventListener('scroll',winScroll);
 	 window.addEventListener('scroll',winScroll);
 	function winScroll(e){ 
 	function winScroll(e){ 
 		if(GetPageScroll().y>220){
 		if(GetPageScroll().y>220){
@@ -204,6 +229,9 @@ function GetPageScroll()
 }
 }
 	</script>
 	</script>
 
 
+<div class="modal_win_bg">
+</div>
+<div id="model_win" class="model_win_container"></div>
 
 
 </body>
 </body>
 </html>
 </html>

+ 2 - 2
app/article/list_new.php

@@ -24,8 +24,8 @@ require_once '../collect/function.php';
     else{
     else{
         $begin = 0;
         $begin = 0;
     }
     }
-    PDO_Connect(""._FILE_DB_USER_ARTICLE_);
-    $query = "SELECT id,title,subtitle,summary,owner,modify_time from article  where status <> 0 ";
+    PDO_Connect(_FILE_DB_USER_ARTICLE_);
+    $query = "SELECT id,title,subtitle,summary,owner,modify_time from article  where status >= 30 ";
     
     
     if(isset($_GET["orderby"])){
     if(isset($_GET["orderby"])){
         switch ($_GET["orderby"]) {
         switch ($_GET["orderby"]) {

+ 18 - 0
app/article/mobile.css

@@ -22,3 +22,21 @@
 #main_view {
 #main_view {
 	padding-top: 10em;
 	padding-top: 10em;
 }
 }
+.other_tran_div > .tool_bar {
+}
+.other_tran_div > .tool_bar > .tool_left {
+	width: calc(100vw - 2em - 12px);
+	max-width: 100%;
+}
+.other_tran_div > .tool_bar > .tool_right {
+	right: 6px;
+	max-width: 50vw;
+}
+#head_span {
+	width: 90vw;
+	display: inline-flex;
+	justify-content: space-between;
+	margin-top: 5px;
+}
+note:hover .ref {
+}

+ 98 - 35
app/article/my_article.js

@@ -1,6 +1,8 @@
 var _display = "para";
 var _display = "para";
+var share_win;
 function my_article_init() {
 function my_article_init() {
 	my_article_list();
 	my_article_list();
+	share_win = iframe_win_init({ container: "share_win", name: "share", width: "500px" });
 	article_add_dlg_init("article_add_div");
 	article_add_dlg_init("article_add_div");
 }
 }
 function my_article_list() {
 function my_article_list() {
@@ -19,35 +21,71 @@ function my_article_list() {
 					//表头
 					//表头
 					html += '<div class="file_list_row" style="padding:5px;">';
 					html += '<div class="file_list_row" style="padding:5px;">';
 					html += '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
 					html += '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
-					html += "<div style='flex:0.5;'>" + key++ + "</div>";
-					html += "<div style='flex:2;'>" + gLocal.gui.title + "</div>";
+					html += "<div style='flex:0.5;'>No.</div>";
+					html += "<div style='flex:4;'>" + gLocal.gui.title + "</div>";
 					html += "<div style='flex:2;'>" + gLocal.gui.privacy + "</div>";
 					html += "<div style='flex:2;'>" + gLocal.gui.privacy + "</div>";
-					html += "<div style='flex:1;'>" + gLocal.gui.copy_link + "</div>";
 					html += "<div style='flex:1;'>" + gLocal.gui.edit + "</a></div>";
 					html += "<div style='flex:1;'>" + gLocal.gui.edit + "</a></div>";
 					html += "<div style='flex:1;'>" + gLocal.gui.preview + "</a></div>";
 					html += "<div style='flex:1;'>" + gLocal.gui.preview + "</a></div>";
-					html += "<div style='flex:1;'>15</div>";
+					html += "<div style='flex:1;'>" + gLocal.gui.copy_link + "</div>";
+					html += "<div style='flex:1;'>" + gLocal.gui.share_to + "</div>";
 					html += "</div>";
 					html += "</div>";
 					//列表
 					//列表
 					for (const iterator of result) {
 					for (const iterator of result) {
 						html += '<div class="file_list_row" style="padding:5px;">';
 						html += '<div class="file_list_row" style="padding:5px;">';
 						html += '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
 						html += '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
 						html += "<div style='flex:0.5;'>" + key++ + "</div>";
 						html += "<div style='flex:0.5;'>" + key++ + "</div>";
-						html += "<div style='flex:2;'>" + iterator.title + "</div>";
+						html += "<div style='flex:4;'>" + iterator.title + "</div>";
 						html += "<div style='flex:2;'>" + render_status(iterator.status) + "</div>";
 						html += "<div style='flex:2;'>" + render_status(iterator.status) + "</div>";
-						html += "<div style='flex:1;'>" + gLocal.gui.copy_link + "</div>";
 						html +=
 						html +=
 							"<div style='flex:1;'><a href='../article/my_article_edit.php?id=" +
 							"<div style='flex:1;'><a href='../article/my_article_edit.php?id=" +
 							iterator.id +
 							iterator.id +
-							"'>" +
+							"' title='" +
 							gLocal.gui.edit +
 							gLocal.gui.edit +
-							"</a></div>";
+							"'>";
+						html += "<button class='icon_btn'>";
+						html += "<svg class='icon'>";
+						html += "<use xlink:href='../studio/svg/icon.svg#ic_mode_edit'></use>";
+						html += "</svg>";
+						html += "</button>";
+
+						html += "</a></div>";
 						html +=
 						html +=
 							"<div style='flex:1;'><a href='../article/?id=" +
 							"<div style='flex:1;'><a href='../article/?id=" +
 							iterator.id +
 							iterator.id +
-							"' target='_blank'>" +
+							"' target='_blank' title='" +
 							gLocal.gui.preview +
 							gLocal.gui.preview +
-							"</a></div>";
-						html += "<div style='flex:1;'>15</div>";
+							"' >";
+						html += "<button class='icon_btn'>";
+						html += "<svg class='icon'>";
+						html += "<use xlink:href='../studio/svg/icon.svg#preview'></use>";
+						html += "</svg>";
+						html += "</button>";
+						html += "</a></div>";
+						html += "<div style='flex:1;'>";
+						html +=
+							"<button class='icon_btn' onclick=\"copy_to_clipboard('www.wikipali.org/mint/app/article/?id=" +
+							iterator.id +
+							"')\" title='" +
+							gLocal.gui.copy_link +
+							"'>";
+						html += "<svg class='icon'>";
+						html += "<use xlink:href='../studio/svg/icon.svg#copy'></use>";
+						html += "</svg>";
+						html += "</button>";
+						html += "</div>";
+						html += "<div style='flex:1;'>";
+						html +=
+							"<button title='" +
+							gLocal.gui.share_to +
+							"' class='icon_btn' onclick=\"article_share('" +
+							iterator.id +
+							"')\">";
+						html += "<svg class='icon'>";
+						html += "<use xlink:href='../studio/svg/icon.svg#share_to'></use>";
+						html += "</svg>";
+						html += "</button>";
+						html += "</div>";
+
 						html += "</div>";
 						html += "</div>";
 					}
 					}
 					$("#article_list").html(html);
 					$("#article_list").html(html);
@@ -60,32 +98,53 @@ function my_article_list() {
 		}
 		}
 	);
 	);
 }
 }
-
-function render_status(status) {
+function article_share(id) {
+	share_win.show("../share/share.php?id=" + id + "&type=3");
+}
+function render_status(status, readonly = true) {
 	status = parseInt(status);
 	status = parseInt(status);
 	let html = "";
 	let html = "";
 	let objStatus = [
 	let objStatus = [
 		{
 		{
-			id: 1,
-			name:
-				"<svg class='icon'><use xlink:href='../studio/svg/icon.svg#ic_lock'></use></svg>" + gLocal.gui.private,
+			id: 10,
+			icon: "<svg class='icon'><use xlink:href='../studio/svg/icon.svg#ic_lock'></use></svg>",
+			name: gLocal.gui.private,
 			tip: gLocal.gui.private_note,
 			tip: gLocal.gui.private_note,
-		},
-		{
-			id: 2,
-			name:
-				"<svg class='icon'><use xlink:href='../studio/svg/icon.svg#eye_disable'></use></svg>" +
-				gLocal.gui.unlisted,
+		} /*
+		,{
+			id: 20,
+			icon: "<svg class='icon'><use xlink:href='../studio/svg/icon.svg#eye_disable'></use></svg>",
+			name: gLocal.gui.unlisted,
 			tip: gLocal.gui.unlisted_note,
 			tip: gLocal.gui.unlisted_note,
-		},
+		}*/,
 		{
 		{
-			id: 3,
-			name:
-				"<svg class='icon'><use xlink:href='../studio/svg/icon.svg#eye_enable'></use></svg>" +
-				gLocal.gui.public,
+			id: 30,
+			icon: "<svg class='icon'><use xlink:href='../studio/svg/icon.svg#eye_enable'></use></svg>",
+			name: gLocal.gui.public,
 			tip: gLocal.gui.public_note,
 			tip: gLocal.gui.public_note,
 		},
 		},
 	];
 	];
+	if (readonly) {
+		for (const iterator of objStatus) {
+			if (iterator.id == status) {
+				return "<div >" + iterator.icon + iterator.name + "</div>";
+			}
+		}
+	} else {
+		let html = "";
+		html += "<select name='status'>";
+		for (const iterator of objStatus) {
+			html += "<option value='" + iterator.id + "' ";
+			if (iterator.id == status) {
+				html += "selected";
+			}
+			html += " >";
+			html += iterator.name;
+			html += "</option>";
+		}
+		html += "</select>";
+		return html;
+	}
 	html += '<div class="case_dropdown"  style="flex:7;">';
 	html += '<div class="case_dropdown"  style="flex:7;">';
 	html += '<input type="hidden" name="status"  value ="' + status + '" />';
 	html += '<input type="hidden" name="status"  value ="' + status + '" />';
 
 
@@ -136,14 +195,18 @@ function my_article_edit(id) {
 					html += "<input type='hidden' name='tag' value='" + result.tag + "'/>";
 					html += "<input type='hidden' name='tag' value='" + result.tag + "'/>";
 					html += "<input type='hidden' name='status' value='" + result.status + "'/>";
 					html += "<input type='hidden' name='status' value='" + result.status + "'/>";
 
 
-					html += "<input type='checkbox' name='import' />" + gLocal.gui.import + gLocal.gui.text;
+					//html += "<input type='checkbox' name='import' />" + gLocal.gui.import + gLocal.gui.text;
 					html += "<div>";
 					html += "<div>";
 					//html += "<div id='article_collect' vui='collect-dlg' ></div>"
 					//html += "<div id='article_collect' vui='collect-dlg' ></div>"
 					html += "<div style='display:flex;'>";
 					html += "<div style='display:flex;'>";
-					html += "<span style='flex:3;margin:auto;'>" + gLocal.gui.title + "</span>";
-					html += '<span id="article_title" style="flex:7;"></span></div>';
+					html += "<span style='flex:1;'>" + gLocal.gui.title + "</span>";
+					html += '<span id="article_title" style="flex:7;"></span>';
+					html += "</div>";
 					html += "<div id='channal_selector' form_name='channal' style='display:none;'></div>";
 					html += "<div id='channal_selector' form_name='channal' style='display:none;'></div>";
-					html += "<div id='aritcle_status' style='display: flex; '></div>";
+					html += "<div style='display:flex;'>";
+					html += "<span style='flex:1;'>" + gLocal.gui.status + "</span>";
+					html += '<span id="aritcle_status" style="flex:7;"></span>';
+					html += "</div>";
 					html +=
 					html +=
 						'<div style="display:none;width:100%;" ><span style="flex:3;margin: auto;">' +
 						'<div style="display:none;width:100%;" ><span style="flex:3;margin: auto;">' +
 						gLocal.gui.language_select +
 						gLocal.gui.language_select +
@@ -159,19 +222,19 @@ function my_article_edit(id) {
 						result.lang +
 						result.lang +
 						'" > <input id="article_lang" type="hidden" name="lang" value=""></div>';
 						'" > <input id="article_lang" type="hidden" name="lang" value=""></div>';
 					html += "<div style='display:flex;'>";
 					html += "<div style='display:flex;'>";
-					html += "<span style='flex:3;margin:auto;'>" + gLocal.gui.introduction + "</span>";
+					html += "<span style='flex:1;margin:auto;'>" + gLocal.gui.introduction + "</span>";
 					html += "<textarea style='flex:7;' name='summary' >" + result.summary + "</textarea></div>";
 					html += "<textarea style='flex:7;' name='summary' >" + result.summary + "</textarea></div>";
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
 
 
 					html +=
 					html +=
-						"<textarea id='article_content' name='content' style='height:500px;max-height: 40vh;'>" +
+						"<textarea id='article_content' name='content' style='height:480px;resize: vertical;'>" +
 						result.content +
 						result.content +
 						"</textarea>";
 						"</textarea>";
 					html += "</div>";
 					html += "</div>";
 
 
 					html += "<div id='preview_div'>";
 					html += "<div id='preview_div'>";
-					html += "<div id='preview_inner' ></div>";
+					html += "<div id='preview_inner' class='sent_mode vertical'></div>";
 					html += "</div>";
 					html += "</div>";
 
 
 					html += "</div>";
 					html += "</div>";
@@ -179,7 +242,7 @@ function my_article_edit(id) {
 					$("#article_list").html(html);
 					$("#article_list").html(html);
 					channal_select_init("channal_selector");
 					channal_select_init("channal_selector");
 					tran_lang_select_init("article_lang_select");
 					tran_lang_select_init("article_lang_select");
-					$("#aritcle_status").html(render_status(result.status));
+					$("#aritcle_status").html(render_status(result.status, false));
 					let html_title =
 					let html_title =
 						"<input id='input_article_title' type='input' name='title' value='" + result.title + "' />";
 						"<input id='input_article_title' type='input' name='title' value='" + result.title + "' />";
 					$("#article_title").html(html_title);
 					$("#article_title").html(html_title);

+ 9 - 5
app/article/my_article_edit.php

@@ -55,7 +55,9 @@ require_once '../studio/index_head.php';
 #preview_inner{
 #preview_inner{
 	background-color: var(--bg-color);
 	background-color: var(--bg-color);
 	color: var(--main-color);
 	color: var(--main-color);
+	padding: 0.5em;
 }
 }
+
 	</style>
 	</style>
 
 
 	<?php
 	<?php
@@ -65,7 +67,7 @@ require_once '../studio/index_head.php';
 	<div class="index_inner " >
 	<div class="index_inner " >
 	<form id="article_edit" action="##" onsubmit="return false"  method="POST" >
 	<form id="article_edit" action="##" onsubmit="return false"  method="POST" >
 	<div class="file_list_block">
 	<div class="file_list_block">
-		<div class="tool_bar" style="width:50%;">
+		<div class="tool_bar" >
 			<div style="display:flex;">
 			<div style="display:flex;">
 
 
 				<span class="icon_btn_div">
 				<span class="icon_btn_div">
@@ -88,18 +90,20 @@ require_once '../studio/index_head.php';
 					</button>
 					</button>
 				</span>
 				</span>
 
 
-				<div id="article_collect" vui='collect-dlg' ></div>
+				
 			</div>
 			</div>
 			<div style="display:flex;">
 			<div style="display:flex;">
-				<div>
-					<button class="icon_btn" title=<?php echo $_local->gui->scan_in_reader ;?>>
+				<div id="article_collect" vui='collect-dlg' ></div>
+				<span class="icon_btn_div">
+					<span class="icon_btn_tip"><?php echo $_local->gui->scan_in_reader ;?></span>
+					<button type="button" class="icon_btn" >
 						<a href="../article/index.php?id=<?php echo $_GET["id"];?>" target="_blank">
 						<a href="../article/index.php?id=<?php echo $_GET["id"];?>" target="_blank">
 							<svg class="icon">
 							<svg class="icon">
 								<use xlink:href="../studio/svg/icon.svg#library"></use>
 								<use xlink:href="../studio/svg/icon.svg#library"></use>
 							</svg>
 							</svg>
 						</a>
 						</a>
 					</button>
 					</button>
-				</div>
+				</span>
 
 
 				<span class="icon_btn_div">
 				<span class="icon_btn_div">
 					<span class="icon_btn_tip"><?php echo $_local->gui->save ;?></span>
 					<span class="icon_btn_tip"><?php echo $_local->gui->save ;?></span>

+ 3 - 1
app/article/my_article_index.php

@@ -66,7 +66,9 @@ require_once '../studio/index_head.php';
 			</div>
 			</div>
 		</div>
 		</div>
 	</div>
 	</div>
-	
+	<div class="modal_win_bg">
+	<div id="share_win" class="iframe_container"></div>
+</div>
 <?php
 <?php
 require_once '../studio/index_foot.php';
 require_once '../studio/index_foot.php';
 ?>
 ?>

+ 20 - 10
app/article/my_article_post.php

@@ -4,17 +4,19 @@ require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
 require_once '../hostsetting/function.php';
 require_once '../hostsetting/function.php';
 require_once "../ucenter/active.php";
 require_once "../ucenter/active.php";
+require_once "../article/function.php";
+require_once "../redis/function.php";
 
 
 add_edit_event(_ARTICLE_EDIT_,$_POST["id"]);
 add_edit_event(_ARTICLE_EDIT_,$_POST["id"]);
 
 
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
 
 
-# 检查是否修改权限
-PDO_Connect(""._FILE_DB_USER_ARTICLE_);
-$query = "SELECT owner FROM  article WHERE id= ?";
-$owner = PDO_FetchOne($query,array($_POST["id"]));
-if($owner!=$_COOKIE["userid"]){
-    $respond["status"]=1;
+# 检查是否修改权限
+$redis = redis_connect();
+$article = new Article($redis); 
+$power = $article->getPower($_POST["id"]);
+if($power<20){
+	$respond["status"]=1;
     $respond["message"]="No Power For Edit";
     $respond["message"]="No Power For Edit";
     echo json_encode($respond, JSON_UNESCAPED_UNICODE);
     echo json_encode($respond, JSON_UNESCAPED_UNICODE);
     exit;
     exit;
@@ -22,6 +24,7 @@ if($owner!=$_COOKIE["userid"]){
 
 
 $_content = $_POST["content"];
 $_content = $_POST["content"];
 
 
+/*
 if($_POST["import"]=='on'){
 if($_POST["import"]=='on'){
     $sent = explode("\n",$_POST["content"]);
     $sent = explode("\n",$_POST["content"]);
     if($sent && count($sent)>0){
     if($sent && count($sent)>0){
@@ -45,7 +48,7 @@ if($_POST["import"]=='on'){
         }
         }
         PDO_Connect(""._FILE_DB_SENTENCE_);
         PDO_Connect(""._FILE_DB_SENTENCE_);
 
 
-        /* 开始一个事务,关闭自动提交 */
+        # 开始一个事务,关闭自动提交 
         $PDO->beginTransaction();
         $PDO->beginTransaction();
         $query="INSERT INTO sentence ('id','block_id','channal','book','paragraph','begin','end','tag','author','editor','text','language','ver','status','strlen','create_time','modify_time','receive_time') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ?, ?)";
         $query="INSERT INTO sentence ('id','block_id','channal','book','paragraph','begin','end','tag','author','editor','text','language','ver','status','strlen','create_time','modify_time','receive_time') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ?, ?)";
         
         
@@ -96,7 +99,7 @@ if($_POST["import"]=='on'){
         $PDO->commit();
         $PDO->commit();
         
         
         if (!$sth || ($sth && $sth->errorCode() != 0)) {
         if (!$sth || ($sth && $sth->errorCode() != 0)) {
-            /*  识别错误且回滚更改  */
+            #  识别错误且回滚更改  
             $PDO->rollBack();
             $PDO->rollBack();
             $error = PDO_ErrorInfo();
             $error = PDO_ErrorInfo();
             $respond['status']=1;
             $respond['status']=1;
@@ -111,8 +114,8 @@ if($_POST["import"]=='on'){
         }		        
         }		        
     }
     }
 }
 }
-
-PDO_Connect(""._FILE_DB_USER_ARTICLE_);
+*/
+PDO_Connect(_FILE_DB_USER_ARTICLE_);
 
 
 $query="UPDATE article SET title = ? , subtitle = ? , summary = ?, content = ?  , tag = ? , setting = ? , status = ? , receive_time= ?  , modify_time= ?   where  id = ?  ";
 $query="UPDATE article SET title = ? , subtitle = ? , summary = ?, content = ?  , tag = ? , setting = ? , status = ? , receive_time= ?  , modify_time= ?   where  id = ?  ";
 $sth = $PDO->prepare($query);
 $sth = $PDO->prepare($query);
@@ -123,5 +126,12 @@ if (!$sth || ($sth && $sth->errorCode() != 0)) {
 	$respond['status']=1;
 	$respond['status']=1;
 	$respond['message']=$error[2];
 	$respond['message']=$error[2];
 }
 }
+else{
+	if($redis){
+		$redis->del("article://".$_POST["id"]);
+		$redis->del("power://article/".$_POST["id"]);
+	}
+	
+}
 echo json_encode($respond, JSON_UNESCAPED_UNICODE);
 echo json_encode($respond, JSON_UNESCAPED_UNICODE);
 ?>
 ?>

+ 20 - 3
app/article/my_article_put.php

@@ -1,4 +1,7 @@
 <?php
 <?php
+/*
+新建文章
+*/
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
@@ -6,15 +9,29 @@ require_once '../hostsetting/function.php';
 require_once "../ucenter/active.php";
 require_once "../ucenter/active.php";
 
 
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
-PDO_Connect(""._FILE_DB_USER_ARTICLE_);
+if(!isset($_COOKIE["userid"])){
+	#不登录不能新建
+	$respond['status']=1;
+	$respond['message']="no power create article";
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;
+}
+if(!isset($_POST["title"])){
+	#无标题不能新建
+	$respond['status']=1;
+	$respond['message']="no title";
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;
+}
+PDO_Connect(_FILE_DB_USER_ARTICLE_);
 
 
 $query="INSERT INTO article ( id,  title  , subtitle  , summary , content   , tag  , owner, setting  , status  , create_time , modify_time , receive_time   )  VALUES  ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ";
 $query="INSERT INTO article ( id,  title  , subtitle  , summary , content   , tag  , owner, setting  , status  , create_time , modify_time , receive_time   )  VALUES  ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ";
 $sth = $PDO->prepare($query);
 $sth = $PDO->prepare($query);
 $uuid = UUID::v4();
 $uuid = UUID::v4();
 //写入日志
 //写入日志
 add_edit_event(_ARTICLE_NEW_,$uuid);
 add_edit_event(_ARTICLE_NEW_,$uuid);
-
-$sth->execute(array($uuid , $_POST["title"] , "" ,"", "" , "" , $_COOKIE["userid"] , "{}" , 1 , mTime() ,  mTime() , mTime() ));
+#新建文章默认私有
+$sth->execute(array($uuid , $_POST["title"] , "" ,"", "" , "" , $_COOKIE["userid"] , "{}" , 10 , mTime() ,  mTime() , mTime() ));
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
 if (!$sth || ($sth && $sth->errorCode() != 0)) {
 if (!$sth || ($sth && $sth->errorCode() != 0)) {
 	$error = PDO_ErrorInfo();
 	$error = PDO_ErrorInfo();

+ 221 - 212
app/article/my_collect.js

@@ -1,229 +1,238 @@
+var share_win;
+
 function my_collect_init() {
 function my_collect_init() {
-  my_collect_list();
-  collect_add_dlg_init("collect_add_div");
+	my_collect_list();
+	share_win = iframe_win_init({ container: "share_win", name: "share", width: "500px" });
+	collect_add_dlg_init("collect_add_div");
 }
 }
 function my_collect_list() {
 function my_collect_list() {
-  $.get(
-    "../article/collect_list.php",
-    {
-      userid: getCookie("userid"),
-      setting: "",
-    },
-    function (data, status) {
-      if (status == "success") {
-        try {
-          let html = "";
-          let result = JSON.parse(data);
-          let key = 1;
-          for (const iterator of result) {
-            html += '<div class="file_list_row" style="padding:5px;">';
-            html +=
-              '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
-            html += "<div style='flex:1;'>" + key++ + "</div>";
-            html += "<div style='flex:2;'>" + iterator.title + "</div>";
-            html +=
-              "<div style='flex:2;'>" +
-              render_status(iterator.status) +
-              "</div>";
-            html += "<div style='flex:1;'>"+gLocal.gui.copy_link+"</div>";
-            html +=
-              "<div style='flex:1;'><a href='../article/my_collect_edit.php?id=" +
-              iterator.id +
-              "'>"+gLocal.gui.edit+"</a></div>";
-            html +=
-              "<div style='flex:1;'><a href='../article/?collect=" +
-              iterator.id +
-              "' target='_blank'>"+gLocal.gui.preview+"</a></div>";
-            html += "<div style='flex:1;'>15</div>";
-            html += "</div>";
-          }
-          $("#article_list").html(html);
-        } catch (e) {
-          console.error(e);
-        }
-      } else {
-        console.error("ajex error");
-      }
-    }
-  );
+	$.get(
+		"../article/collect_list.php",
+		{
+			userid: getCookie("userid"),
+			setting: "",
+		},
+		function (data, status) {
+			if (status == "success") {
+				try {
+					let html = "";
+					let result = JSON.parse(data);
+					let key = 1;
+					for (const iterator of result) {
+						html += '<div class="file_list_row" style="padding:5px;">';
+						html += '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
+						html += "<div style='flex:1;'>" + key++ + "</div>";
+						html += "<div style='flex:2;'>" + iterator.title + "</div>";
+						html += "<div style='flex:2;'>" + render_status(iterator.status) + "</div>";
+						html += "<div style='flex:1;'>" + gLocal.gui.copy_link + "</div>";
+						html +=
+							"<div style='flex:1;'><a href='../article/my_collect_edit.php?id=" +
+							iterator.id +
+							"'>" +
+							gLocal.gui.edit +
+							"</a></div>";
+						html +=
+							"<div style='flex:1;'><a href='../article/?collect=" +
+							iterator.id +
+							"' target='_blank'>" +
+							gLocal.gui.preview +
+							"</a></div>";
+						html += "<div style='flex:1;'>";
+						html += "<a onclick=\"collection_share('" + iterator.id + "')\">share</a>";
+						html += "</div>";
+						html += "</div>";
+					}
+					$("#article_list").html(html);
+				} catch (e) {
+					console.error(e);
+				}
+			} else {
+				console.error("ajex error");
+			}
+		}
+	);
 }
 }
 var _arrArticleList;
 var _arrArticleList;
 var _arrArticleOrder = new Array();
 var _arrArticleOrder = new Array();
 function my_collect_edit(id) {
 function my_collect_edit(id) {
-  $.get(
-    "../article/collect_get.php",
-    {
-      id: id,
-      setting: "",
-    },
-    function (data, status) {
-      if (status == "success") {
-        try {
-          let html = "";
-          let result = JSON.parse(data);
-          $("#article_collect").attr("a_id", result.id);
-          html += '<div class="" style="padding:5px;">';
-          html += '<div style="max-width:2em;flex:1;"></div>';
-          html += "<input type='hidden' name='id' value='" + result.id + "'/>";
-          html +=
-            "<input type='hidden' name='subtitle' value='" +
-            result.subtitle +
-            "'/>";
-          html +=
-            "<input type='hidden' name='summary' value='" +
-            result.summary +
-            "'/>";
-          html +=
-            "<input type='hidden' name='status' value='" +
-            result.status +
-            "'/>";
-          html +=
-            "<input type='hidden' name='lang' value='" + result.lang + "'/>";
-          html +=
-            "<input id='form_article_list' type='hidden' name='article_list' value='" +
-            result.article_list +
-            "'/>";
-          html += "</div>";
-          html += "<div style='display:flex;'>";
-          html += "<div style='flex:4;'>";
-
-          _arrArticleList = JSON.parse(result.article_list);
-          html += "<ul id='ul_article_list'>";
-          for (let index = 0; index < _arrArticleList.length; index++) {
-            const element = _arrArticleList[index];
-            html += my_collect_render_article(index, element);
-            _arrArticleOrder.push(index);
-          }
-
-          html += "</ul>";
-
-          html += "</div>";
-
-          html += "<div id='preview_div'>";
-          html += "<div id='preview_inner' ></div>";
-          html += "</div>";
-
-          html += "</div>";
-
-          $("#article_list").html(html);
-          $("#collect_title").val(result.title);
-
-          $("#ul_article_list").sortable({
-            update: function (event, ui) {
-              let sortedIDs = $("#ul_article_list").sortable("toArray");
-              _arrArticleOrder = new Array();
-              for (const iSorted of sortedIDs) {
-                let newindex = parseInt($("#" + iSorted).attr("article_index"));
-                _arrArticleOrder.push(_arrArticleList[newindex]);
-              }
-              $("#form_article_list").val(JSON.stringify(_arrArticleOrder));
-            },
-          });
-
-          $("#aritcle_status").html(render_status(result.status));
-          let html_title =
-            "<input id='input_article_title' type='input' name='title' value='" +
-            result.title +
-            "' />";
-          $("#article_title").html(html_title);
-        } catch (e) {
-          console.error(e);
-        }
-      } else {
-        console.error("ajex error");
-      }
-    }
-  );
+	$.get(
+		"../article/collect_get.php",
+		{
+			id: id,
+			setting: "",
+		},
+		function (data, status) {
+			if (status == "success") {
+				try {
+					let html = "";
+					let result = JSON.parse(data);
+					$("#article_collect").attr("a_id", result.id);
+					html += '<div class="" style="padding:5px;">';
+					html += '<div style="max-width:2em;flex:1;"></div>';
+					html += "<input type='hidden' name='id' value='" + result.id + "'/>";
+
+					html += "<div style='display:flex;'>";
+					html += "<div style='flex:2;'>" + gLocal.gui.title + "</div>";
+					html += "<div style='flex:8;'>";
+					html += "<input type='input' name='title' value='" + result.title + "'/>";
+					html += "</div></div>";
+
+					html += "<div style='display:flex;'>";
+					html += "<div style='flex:2;'>" + gLocal.gui.sub_title + "</div>";
+					html += "<div style='flex:8;'>";
+					html += "<input type='input' name='subtitle' value='" + result.subtitle + "'/>";
+					html += "</div></div>";
+
+					html += "<div style='display:flex;'>";
+					html += "<div style='flex:2;'>" + gLocal.gui.summary + "</div>";
+					html += "<div style='flex:8;'>";
+					html += "<input type='input' name='summary' value='" + result.summary + "'/>";
+					html += "</div></div>";
+
+					html += "<div style='display:flex;'>";
+					html += "<div style='flex:2;'>" + gLocal.gui.status + "</div>";
+					html += "<div style='flex:8;'>";
+					html += render_status(result.status, false);
+					html += "</div></div>";
+
+					html += "<div style='display:flex;'>";
+					html += "<div style='flex:2;'>" + gLocal.gui.language + "</div>";
+					html += "<div style='flex:8;'>";
+					html += "<input type='input' name='lang' value='" + result.lang + "'/>";
+					html += "</div></div>";
+
+					html +=
+						"<input id='form_article_list' type='hidden' name='article_list' value='" +
+						result.article_list +
+						"'/>";
+					html += "</div>";
+					html += "<div style='display:flex;'>";
+					html += "<div style='flex:4;'>";
+
+					_arrArticleList = JSON.parse(result.article_list);
+					html += "<ul id='ul_article_list'>";
+					for (let index = 0; index < _arrArticleList.length; index++) {
+						const element = _arrArticleList[index];
+						html += my_collect_render_article(index, element);
+						_arrArticleOrder.push(index);
+					}
+
+					html += "</ul>";
+
+					html += "</div>";
+
+					html += "<div id='preview_div'>";
+					html += "<div id='preview_inner' ></div>";
+					html += "</div>";
+
+					html += "</div>";
+
+					$("#article_list").html(html);
+					$("#collection_title").html(result.title);
+
+					$("#ul_article_list").sortable({
+						update: function (event, ui) {
+							let sortedIDs = $("#ul_article_list").sortable("toArray");
+							_arrArticleOrder = new Array();
+							for (const iSorted of sortedIDs) {
+								let newindex = parseInt($("#" + iSorted).attr("article_index"));
+								_arrArticleOrder.push(_arrArticleList[newindex]);
+							}
+							$("#form_article_list").val(JSON.stringify(_arrArticleOrder));
+						},
+					});
+				} catch (e) {
+					console.error(e);
+				}
+			} else {
+				console.error("ajex error");
+			}
+		}
+	);
 }
 }
 
 
 function my_collect_render_article(index, article) {
 function my_collect_render_article(index, article) {
-  let html = "";
-  html +=
-    "<li id='article_item_" +
-    index +
-    "' article_index='" +
-    index +
-    "' class='file_list_row'>";
-  html += "<span style='flex:1;'>";
-  html += "<select>";
-  let selected = "";
-  for (let i = 1; i < 9; i++) {
-    if (parseInt(article.level) == i) {
-      selected = "selected";
-    } else {
-      selected = "";
-    }
-    html += "<option " + selected + " value='" + i + "' >H " + i + "</option>";
-  }
-  html += "</select>";
-  html += "</span>";
-  html += "<span style='flex:3;'>";
-  html +=
-    "<a href='../article/my_article_edit.php?id=" + article.article + "'>";
-  html += article.title;
-  html += "</a>";
-  html += "</span>";
-  html +=
-    "<span style='flex:1;' onclick=\"article_preview('" +
-    article.article +
-    "')\">";
-  html += "Preview";
-  html += "</span>";
-  html += "</li>";
-  return html;
+	let html = "";
+	html += "<li id='article_item_" + index + "' article_index='" + index + "' class='file_list_row'>";
+	html += "<span style='flex:1;'>";
+	html += "<select>";
+	let selected = "";
+	for (let i = 1; i < 9; i++) {
+		if (parseInt(article.level) == i) {
+			selected = "selected";
+		} else {
+			selected = "";
+		}
+		html += "<option " + selected + " value='" + i + "' >H " + i + "</option>";
+	}
+	html += "</select>";
+	html += "</span>";
+	html += "<span style='flex:3;'>";
+	html += "<a href='../article/my_article_edit.php?id=" + article.article + "'>";
+	html += article.title;
+	html += "</a>";
+	html += "</span>";
+	html += "<span style='flex:1;' onclick=\"article_preview('" + article.article + "')\">";
+	html += "Preview";
+	html += "</span>";
+	html += "</li>";
+	return html;
 }
 }
 
 
 function article_preview(id) {
 function article_preview(id) {
-  $.get(
-    "../article/get.php",
-    {
-      id: id,
-      setting: "",
-    },
-    function (data, status) {
-      if (status == "success") {
-        try {
-          let html = "";
-          let result = JSON.parse(data);
-          $("#preview_inner").html(note_init(result.content));
-          note_refresh_new();
-        } catch (e) {
-          console.error(e.message);
-        }
-      }
-    }
-  );
+	$.get(
+		"../article/get.php",
+		{
+			id: id,
+			setting: "",
+		},
+		function (data, status) {
+			if (status == "success") {
+				try {
+					let html = "";
+					let result = JSON.parse(data);
+					$("#preview_inner").html(note_init(result.content));
+					note_refresh_new();
+				} catch (e) {
+					console.error(e.message);
+				}
+			}
+		}
+	);
 }
 }
 
 
 function my_collect_save() {
 function my_collect_save() {
-  $.ajax({
-    type: "POST", //方法类型
-    dataType: "json", //预期服务器返回的数据类型
-    url: "../article/my_collect_post.php", //url
-    data: $("#collect_edit").serialize(),
-    success: function (result) {
-      console.log(result); //打印服务端返回的数据(调试用)
-
-      if (result.status == 0) {
-        alert("保存成功");
-      } else {
-        alert("error:" + result.message);
-      }
-    },
-    error: function (data, status) {
-      alert("异常!" + data.responseText);
-      switch (status) {
-        case "timeout":
-          break;
-        case "error":
-          break;
-        case "notmodified":
-          break;
-        case "parsererror":
-          break;
-        default:
-          break;
-      }
-    },
-  });
+	$.ajax({
+		type: "POST", //方法类型
+		dataType: "json", //预期服务器返回的数据类型
+		url: "../article/my_collect_post.php", //url
+		data: $("#collect_edit").serialize(),
+		success: function (result) {
+			console.log(result); //打印服务端返回的数据(调试用)
+
+			if (result.status == 0) {
+				alert("保存成功");
+			} else {
+				alert("error:" + result.message);
+			}
+		},
+		error: function (data, status) {
+			alert("异常!" + data.responseText);
+			switch (status) {
+				case "timeout":
+					break;
+				case "error":
+					break;
+				case "notmodified":
+					break;
+				case "parsererror":
+					break;
+				default:
+					break;
+			}
+		},
+	});
+}
+function collection_share(id) {
+	share_win.show("../share/share.php?id=" + id + "&type=4");
 }
 }

+ 5 - 2
app/article/my_collect_edit.php

@@ -3,6 +3,7 @@ require_once '../studio/index_head.php';
 ?>
 ?>
 <body id="file_list_body" >
 <body id="file_list_body" >
 
 
+	<script language="javascript" src="../article/my_article.js"></script>
 	<script language="javascript" src="../article/my_collect.js"></script>
 	<script language="javascript" src="../article/my_collect.js"></script>
 	<script language="javascript" src="../term/note.js"></script>
 	<script language="javascript" src="../term/note.js"></script>
 	<script language="javascript" src="../term/term.js"></script>
 	<script language="javascript" src="../term/term.js"></script>
@@ -17,7 +18,6 @@ require_once '../studio/index_head.php';
 	<style>
 	<style>
 	#collect {
 	#collect {
 		background-color: var(--btn-border-color);
 		background-color: var(--btn-border-color);
-		
 	}
 	}
 	#collect:hover{
 	#collect:hover{
 		background-color: var(--btn-border-color);
 		background-color: var(--btn-border-color);
@@ -59,7 +59,7 @@ require_once '../studio/index_head.php';
 		<div class="tool_bar">
 		<div class="tool_bar">
 			<div style="display:flex;">
 			<div style="display:flex;">
 				<a href="../article/my_collect_index.php">返回</a>
 				<a href="../article/my_collect_index.php">返回</a>
-				<span><input  id="collect_title" type='input' name='title' value="" /></span>
+				<span id='collection_title'></span>
 			</div>
 			</div>
 			<div style="display:flex;">
 			<div style="display:flex;">
 				<div id="aritcle_status"></div>
 				<div id="aritcle_status"></div>
@@ -83,6 +83,9 @@ require_once '../studio/index_head.php';
 			</div>
 			</div>
 		</div>
 		</div>
 
 
+		<div id="collection_info"  class="file_list_block" style="">
+
+		</div>
 		<div id="article_list"  class="file_list_block" style="">
 		<div id="article_list"  class="file_list_block" style="">
 
 
 		</div>
 		</div>

+ 2 - 1
app/article/my_collect_index.php

@@ -57,7 +57,8 @@ require_once '../studio/index_tool_bar.php';
 			</div>
 			</div>
 		</div>
 		</div>
 	</div>
 	</div>
-	
+	<div class="modal_win_bg">
+	<div id="share_win" class="iframe_container"></div>
 <?php
 <?php
 require_once '../studio/index_foot.php';
 require_once '../studio/index_foot.php';
 ?>
 ?>

+ 35 - 7
app/article/my_collect_post.php

@@ -2,14 +2,34 @@
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
-require_once '../hostsetting/function.php';
+require_once '../collect/function.php';
 require_once "../ucenter/active.php";
 require_once "../ucenter/active.php";
+require_once "../redis/function.php";
 
 
-add_edit_event(_COLLECTION_EDIT_,$_POST["id"]);
 
 
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
+if(!isset($_COOKIE["userid"])){
+	#不登录不能新建
+	$respond['status']=1;
+	$respond['message']="no power create article";
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;
+}
+# 检查当前用户是否有修改权限
+$redis = redis_connect();
+$collection = new CollectInfo($redis); 
+$power = $collection->getPower($_POST["id"]);
+if($power<20){
+	$respond["status"]=1;
+    $respond["message"]="No Power For Edit";
+    echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+    exit;
+}
+
+
+add_edit_event(_COLLECTION_EDIT_,$_POST["id"]);
 
 
-PDO_Connect(""._FILE_DB_USER_ARTICLE_);
+PDO_Connect(_FILE_DB_USER_ARTICLE_);
 
 
 $query="UPDATE collect SET title = ? , subtitle = ? , summary = ?, article_list = ?  ,  status = ? , lang = ? , receive_time= ?  , modify_time= ?   where  id = ?  ";
 $query="UPDATE collect SET title = ? , subtitle = ? , summary = ?, article_list = ?  ,  status = ? , lang = ? , receive_time= ?  , modify_time= ?   where  id = ?  ";
 $sth = $PDO->prepare($query);
 $sth = $PDO->prepare($query);
@@ -21,17 +41,25 @@ if (!$sth || ($sth && $sth->errorCode() != 0)) {
 	$respond['message']=$error[2];
 	$respond['message']=$error[2];
 }
 }
 else{
 else{
+	if($redis){
+		$redis->del("collection://".$_POST["id"]);
+		$redis->del("power://collection/".$_POST["id"]);
+	}
     # 更新 article_list 表
     # 更新 article_list 表
     $query = "DELETE FROM article_list WHERE collect_id = ? ";
     $query = "DELETE FROM article_list WHERE collect_id = ? ";
-     PDO_Execute($query,array($_POST["id"]));
-     $arrList = json_decode($_POST["article_list"]);
-     if(count($arrList)>0){
+    PDO_Execute($query,array($_POST["id"]));
+    $arrList = json_decode($_POST["article_list"]);
+    if(count($arrList)>0){
         /* 开始一个事务,关闭自动提交 */
         /* 开始一个事务,关闭自动提交 */
         $PDO->beginTransaction();
         $PDO->beginTransaction();
         $query = "INSERT INTO article_list (collect_id, article_id,level,title) VALUES ( ?, ?, ? , ? )";
         $query = "INSERT INTO article_list (collect_id, article_id,level,title) VALUES ( ?, ?, ? , ? )";
         $sth = $PDO->prepare($query);
         $sth = $PDO->prepare($query);
         foreach ($arrList as $row) {
         foreach ($arrList as $row) {
             $sth->execute(array($_POST["id"],$row->article,$row->level,$row->title));
             $sth->execute(array($_POST["id"],$row->article,$row->level,$row->title));
+			if($redis){
+				#删除article权限缓存
+				$redis->del("power://article/".$row->article);
+			}
         }
         }
         $PDO->commit();
         $PDO->commit();
         if (!$sth || ($sth && $sth->errorCode() != 0)) {
         if (!$sth || ($sth && $sth->errorCode() != 0)) {
@@ -41,7 +69,7 @@ else{
             $respond['status']=1;
             $respond['status']=1;
             $respond['message']=$error[2];
             $respond['message']=$error[2];
         }
         }
-     }
+    }
 }
 }
 echo json_encode($respond, JSON_UNESCAPED_UNICODE);
 echo json_encode($respond, JSON_UNESCAPED_UNICODE);
 ?>
 ?>

+ 16 - 2
app/article/my_collect_put.php

@@ -6,12 +6,26 @@ require_once '../hostsetting/function.php';
 require_once "../ucenter/active.php";
 require_once "../ucenter/active.php";
 
 
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
-PDO_Connect(""._FILE_DB_USER_ARTICLE_);
+if(!isset($_COOKIE["userid"])){
+	#不登录不能新建
+	$respond['status']=1;
+	$respond['message']="no power create article";
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;
+}
+if(!isset($_POST["title"])){
+	#无标题不能新建
+	$respond['status']=1;
+	$respond['message']="no title";
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;
+}
+add_edit_event(_COLLECTION_NEW_,$uuid);
 
 
+PDO_Connect(""._FILE_DB_USER_ARTICLE_);
 $query="INSERT INTO collect ( id,  title  , subtitle  , summary , article_list   , owner, lang  , status  , create_time , modify_time , receive_time   )  VALUES  ( ? , ? , ? , ?  , ? , ? , ? , ? , ? , ? , ? ) ";
 $query="INSERT INTO collect ( id,  title  , subtitle  , summary , article_list   , owner, lang  , status  , create_time , modify_time , receive_time   )  VALUES  ( ? , ? , ? , ?  , ? , ? , ? , ? , ? , ? , ? ) ";
 $sth = $PDO->prepare($query);
 $sth = $PDO->prepare($query);
 $uuid = UUID::v4();
 $uuid = UUID::v4();
-add_edit_event(_COLLECTION_NEW_,$uuid);
 $sth->execute(array($uuid , $_POST["title"] , "" ,"", "[]" ,  $_COOKIE["userid"] , "" , 1 , mTime() ,  mTime() , mTime() ));
 $sth->execute(array($uuid , $_POST["title"] , "" ,"", "[]" ,  $_COOKIE["userid"] , "" , 1 , mTime() ,  mTime() , mTime() ));
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
 if (!$sth || ($sth && $sth->errorCode() != 0)) {
 if (!$sth || ($sth && $sth->errorCode() != 0)) {

+ 4 - 2
app/article/style.css

@@ -20,7 +20,7 @@
 	/*height: 5em;*/
 	/*height: 5em;*/
 	background-color: var(--tool-bg-color1);
 	background-color: var(--tool-bg-color1);
 	border-bottom: 1px solid var(--tool-line-color);
 	border-bottom: 1px solid var(--tool-line-color);
-	padding: 10px;
+	padding: 0.5vh 0.5vw;
 	margin-top: 50px;
 	margin-top: 50px;
 	position: fixed;
 	position: fixed;
 	top: 0;
 	top: 0;
@@ -83,7 +83,6 @@
 }
 }
 #right_pannal {
 #right_pannal {
 	flex: 3;
 	flex: 3;
-	max-width: 20em;
 }
 }
 #head_bar {
 #head_bar {
 	height: unset;
 	height: unset;
@@ -160,3 +159,6 @@ img {
     flex: 5;
     flex: 5;
 }
 }
 */
 */
+.tool_bar {
+	display: flex;
+}

+ 50 - 32
app/calendar/index.html

@@ -428,46 +428,47 @@
 				re_loc: "relocation",
 				re_loc: "relocation",
 			};
 			};
 			var pali_month_name = [
 			var pali_month_name = [
-				{ id: "1", value: "jeṭṭha", season: "gimhāna", season_icon: "☀" }, //5.X-四-十五-心
-				{ id: "2", value: "asāḷha", season: "gimhāna", season_icon: "☀" }, //6.X-五-十五、十六-箕、斗
-				{ id: "3", value: "sāvana", season: "vassāna", season_icon: "☔" }, //7.X-六-十五-女
-				{ id: "4", value: "poṭṭhapāda", season: "vassāna", season_icon: "☔" }, //8.x-七-十五、十六-室、壁
-				{ id: "5", value: "assajuja", season: "vassāna", season_icon: "☔" }, //9.X-八-十五-樓
-				{ id: "6", value: "kattika", season: "vassāna", season_icon: "☔" }, //10.X-九-十五-昂
-				{ id: "7", value: "māgasira", season: "hemanta", season_icon: "❄" }, //11.X-十-十五-觜
-				{ id: "8", value: "phussa", season: "hemanta", season_icon: "❄" }, //12.X-十一-十五-鬼
-				{ id: "9", value: "māgha", season: "hemanta", season_icon: "❄" }, //1.X-十二-十五-星
-				{ id: "10", value: "phagguna", season: "hemanta", season_icon: "❄" }, //2.X-正月-十四、十五-張、異
-				{ id: "11", value: "citta", season: "gimhāna", season_icon: "☀" }, //3.X-二月-十五-角
-				{ id: "12", value: "vesākha", season: "gimhāna", season_icon: "☀" }, //4.X-三月-十五-氐
+				{ id: "5", position: 0, value: "assajuja", season: "vassāna", season_icon: "☔" }, //9.X-八-十五-樓
+				{ id: "6", position: 1.25, value: "kattika", season: "vassāna", season_icon: "☔" }, //10.X-九-十五-昂
+				{ id: "7", position: 3.5, value: "māgasira", season: "hemanta", season_icon: "❄" }, //11.X-十-十五-觜
+				{ id: "8", position: 5.75, value: "phussa", season: "hemanta", season_icon: "❄" }, //12.X-十一-十五-鬼
+				{ id: "9", position: 8, value: "māgha", season: "hemanta", season_icon: "❄" }, //1.X-十二-十五-星
+				{ id: "10", position: 10, value: "phagguna", season: "hemanta", season_icon: "❄" }, //2.X-正月-十四、十五-張、異
+				{ id: "11", position: 12.25, value: "citta", season: "gimhāna", season_icon: "☀" }, //3.X-二月-十五-角
+				{ id: "12", position: 14.5, value: "vesākha", season: "gimhāna", season_icon: "☀" }, //4.X-三月-十五-氐
+				{ id: "1", position: 16.75, value: "jeṭṭha", season: "gimhāna", season_icon: "☀" }, //5.X-四-十五-心
+				{ id: "2", position: 18.75, value: "asāḷha", season: "gimhāna", season_icon: "☀" }, //6.X-五-十五、十六-箕、斗
+				{ id: "3", position: 21, value: "sāvana", season: "vassāna", season_icon: "☔" }, //7.X-六-十五-女
+				{ id: "4", position: 23.5, value: "poṭṭhapāda", season: "vassāna", season_icon: "☔" }, //8.x-七-十五、十六-室、壁
+				{ id: "5", position: 26, value: "assajuja", season: "vassāna", season_icon: "☔" }, //9.X-八-十五-樓
 			];
 			];
 			var pali_nakkhatta_name = [
 			var pali_nakkhatta_name = [
-				{ id: "0", value: "assayuja", name_zh: "娄宿" },
+				{ id: "0", value: "assayuja", name_zh: "娄宿" },//V3
 				{ id: "1", value: "bharaṇī", name_zh: "胃宿" },
 				{ id: "1", value: "bharaṇī", name_zh: "胃宿" },
-				{ id: "2", value: "Kattikā", name_zh: "昂宿" },
+				{ id: "2", value: "Kattikā", name_zh: "昂宿" },//V4
 				{ id: "3", value: "rohiṇī", name_zh: "毕宿" },
 				{ id: "3", value: "rohiṇī", name_zh: "毕宿" },
-				{ id: "4", value: "magasira", name_zh: "觜宿" },
+				{ id: "4", value: "magasira", name_zh: "觜宿" },//H1
 				{ id: "5", value: "Addā", name_zh: "参宿" },
 				{ id: "5", value: "Addā", name_zh: "参宿" },
 				{ id: "6", value: "punabbasu", name_zh: "井宿" },
 				{ id: "6", value: "punabbasu", name_zh: "井宿" },
-				{ id: "7", value: "phussa", name_zh: "鬼宿" },
+				{ id: "7", value: "phussa", name_zh: "鬼宿" },//H2
 				{ id: "8", value: "Asilesā", name_zh: "柳宿" },
 				{ id: "8", value: "Asilesā", name_zh: "柳宿" },
-				{ id: "9", value: "maghā", name_zh: "星宿" },
-				{ id: "10", value: "pubbaphagguṇī", name_zh: "张宿" },
-				{ id: "11", value: "uttaraphagguṇī", name_zh: "翼宿" },
+				{ id: "9", value: "maghā", name_zh: "星宿" },//H3
+				{ id: "10", value: "pubbaphagguṇī", name_zh: "张宿" },//H4
+				{ id: "11", value: "uttaraphagguṇī", name_zh: "翼宿" },//H4
 				{ id: "12", value: "hattha", name_zh: "轸宿" },
 				{ id: "12", value: "hattha", name_zh: "轸宿" },
-				{ id: "13", value: "cittā", name_zh: "角宿" },
+				{ id: "13", value: "cittā", name_zh: "角宿" },//G1
 				{ id: "14", value: "sāti", name_zh: "亢宿" },
 				{ id: "14", value: "sāti", name_zh: "亢宿" },
-				{ id: "15", value: "visākhā", name_zh: "氐宿" },
+				{ id: "15", value: "visākhā", name_zh: "氐宿" },//G2
 				{ id: "16", value: "anurādhā", name_zh: "房宿" },
 				{ id: "16", value: "anurādhā", name_zh: "房宿" },
-				{ id: "17", value: "jeṭṭha", name_zh: "心宿" },
+				{ id: "17", value: "jeṭṭha", name_zh: "心宿" },//G3
 				{ id: "18", value: "mūlā", name_zh: "尾宿" },
 				{ id: "18", value: "mūlā", name_zh: "尾宿" },
-				{ id: "19", value: "pubbāsāḷha", name_zh: "箕宿" },
-				{ id: "20", value: "uttarāsāḷha", name_zh: "斗宿" },
-				{ id: "21", value: "savaṇa", name_zh: "女宿" },
+				{ id: "19", value: "pubbāsāḷha", name_zh: "箕宿" },//G4
+				{ id: "20", value: "uttarāsāḷha", name_zh: "斗宿" },//G4
+				{ id: "21", value: "savaṇa", name_zh: "女宿" },//V1
 				{ id: "22", value: "dhaniṭṭhā", name_zh: "虚宿" },
 				{ id: "22", value: "dhaniṭṭhā", name_zh: "虚宿" },
 				{ id: "23", value: "satabhisaja", name_zh: "危宿" },
 				{ id: "23", value: "satabhisaja", name_zh: "危宿" },
-				{ id: "24", value: "pubbabhaddapadā", name_zh: "室宿" },
-				{ id: "25", value: "uttarabhaddapadā", name_zh: "壁宿" },
+				{ id: "24", value: "pubbabhaddapadā", name_zh: "室宿" },//V2
+				{ id: "25", value: "uttarabhaddapadā", name_zh: "壁宿" },//V2
 				{ id: "26", value: "revatī", name_zh: "奎宿" },
 				{ id: "26", value: "revatī", name_zh: "奎宿" },
 			];
 			];
 			var horoscope_name = [
 			var horoscope_name = [
@@ -630,14 +631,25 @@ function show_time() {
 			//根据;
 			//根据;
 			function get_station_name(data_time) {
 			function get_station_name(data_time) {
 				//获取星象信息
 				//获取星象信息
+				var x_m=0;
 				let solar_num = 0;
 				let solar_num = 0;
 				let lunar_num = 0;
 				let lunar_num = 0;
+				let result = new Object();
 				solar_num = get_position(data_time).solar_station;
 				solar_num = get_position(data_time).solar_station;
 				solar_num = Math.floor(solar_num);
 				solar_num = Math.floor(solar_num);
 				lunar_num = get_position(data_time).lunar_station;
 				lunar_num = get_position(data_time).lunar_station;
+				for(x_m in pali_month_name){
+					if(lunar_num>pali_month_name[x_m].position && lunar_num<pali_month_name[Number(x_m)+1].position){
+						result.month_name=pali_month_name[x_m]
+						break;
+					} else if(lunar_num<1.25 || lunar_num>=26){
+						result.month_name=pali_month_name[0]
+						break;
+					}
+					
+				}
 				lunar_num = Math.floor(lunar_num);
 				lunar_num = Math.floor(lunar_num);
 
 
-				let result = new Object();
 				result.solar = horoscope_name[solar_num]; //黄道十二宫星象名称
 				result.solar = horoscope_name[solar_num]; //黄道十二宫星象名称
 				result.lunar = pali_nakkhatta_name[lunar_num]; //27星宿月站名称
 				result.lunar = pali_nakkhatta_name[lunar_num]; //27星宿月站名称
 				return result;
 				return result;
@@ -790,7 +802,7 @@ function angle_trans(angle,type) {
 					date.setTime(today.getTime() + i * 3600 * 24 * 1000);
 					date.setTime(today.getTime() + i * 3600 * 24 * 1000);
 					let info_obj = new Object();
 					let info_obj = new Object();
 					info_obj.id = "dawn" + i;
 					info_obj.id = "dawn" + i;
-					info_obj.title = localString[g_language].twilight_time;
+					info_obj.title = "🌆"+localString[g_language].twilight_time;
 					info_obj.start = SunCalc.getTimes(
 					info_obj.start = SunCalc.getTimes(
 						date,
 						date,
 						g_coordinate_this.AT,
 						g_coordinate_this.AT,
@@ -801,7 +813,7 @@ function angle_trans(angle,type) {
 
 
 					info_obj = new Object();
 					info_obj = new Object();
 					info_obj.id = "noon" + i;
 					info_obj.id = "noon" + i;
-					info_obj.title = localString[g_language].noon_time;
+					info_obj.title = "🌞"+localString[g_language].noon_time;
 					info_obj.start = SunCalc.getTimes(
 					info_obj.start = SunCalc.getTimes(
 						date,
 						date,
 						g_coordinate_this.AT,
 						g_coordinate_this.AT,
@@ -812,7 +824,7 @@ function angle_trans(angle,type) {
 					g_event_list_arr.push(info_obj);
 					g_event_list_arr.push(info_obj);
 					info_obj = new Object();
 					info_obj = new Object();
 					info_obj.id = "sunset" + i;
 					info_obj.id = "sunset" + i;
-					info_obj.title = localString[g_language].sun_set;
+					info_obj.title = "🌇"+localString[g_language].sun_set;
 					info_obj.start = SunCalc.getTimes(
 					info_obj.start = SunCalc.getTimes(
 						date,
 						date,
 						g_coordinate_this.AT,
 						g_coordinate_this.AT,
@@ -852,7 +864,13 @@ function angle_trans(angle,type) {
 				for (i_full in full_list) {
 				for (i_full in full_list) {
 					info_obj = new Object();
 					info_obj = new Object();
 					info_obj.id = "full_moon" + i_full;
 					info_obj.id = "full_moon" + i_full;
-					info_obj.title = "🌕" + get_station_name(full_list[i_full]).lunar.value;
+					info_obj.title = "🌕@" + get_station_name(full_list[i_full]).lunar.value + "🌟";
+					info_obj.start = full_list[i_full];
+					g_event_list_arr.push(info_obj);
+
+					info_obj = new Object();
+					info_obj.id = "month" + i_full;
+					info_obj.title =get_station_name(full_list[i_full]).month_name.value+localString[g_language].month;
 					info_obj.start = full_list[i_full];
 					info_obj.start = full_list[i_full];
 					g_event_list_arr.push(info_obj);
 					g_event_list_arr.push(info_obj);
 				}
 				}

+ 34 - 0
app/channal/card.php

@@ -0,0 +1,34 @@
+<?php
+//
+
+require_once "../path.php";
+require_once "../public/_pdo.php";
+require_once '../ucenter/function.php';
+
+
+if (isset($_GET["id"])) {
+	$output["id"]=$_GET["id"];
+	PDO_Connect( _FILE_DB_CHANNAL_);
+	$query = "SELECT name,create_time,owner,summary FROM channal  WHERE id = ? ";
+	$channel = PDO_FetchRow($query, array($_GET["id"]));
+	$strData="";
+	if ($channel) {
+		
+		$_userinfo = new UserInfo();
+		$name = $_userinfo->getName($channel["owner"]);
+
+		$strData .= "<div>名称:".$channel["name"]."</div>";
+		$strData .=  "<div>创建人:".$name["nickname"]."</div>";
+		$strData .=  "<div>创建时间:".date("Y/m/d",$channel["create_time"]/1000)."</div>";
+		$strData .=  "<div>简介:".$channel["summary"]."</div>";
+	} else {
+		$strData .=  "unkow";
+	}
+	$output["data"] = $strData;
+} else {
+	$output["id"]=0;
+	$output["data"] = "unkow";
+}
+echo json_encode($output, JSON_UNESCAPED_UNICODE);
+
+?>

+ 68 - 17
app/channal/channal.js

@@ -2,9 +2,10 @@ var _my_channal = null;
 var gChannelId;
 var gChannelId;
 var get_channel_list_callback = null;
 var get_channel_list_callback = null;
 channal_list();
 channal_list();
-
+var share_win;
 function channal_list_init() {
 function channal_list_init() {
 	my_channal_list();
 	my_channal_list();
+	share_win = iframe_win_init({ container: "share_win", name: "share", width: "500px" });
 	channal_add_dlg_init("channal_add_div");
 	channal_add_dlg_init("channal_add_div");
 }
 }
 function channal_list() {
 function channal_list() {
@@ -45,12 +46,18 @@ function my_channal_list() {
 						html += '<div class="file_list_row" style="padding:5px;">';
 						html += '<div class="file_list_row" style="padding:5px;">';
 						html += '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
 						html += '<div style="max-width:2em;flex:1;"><input type="checkbox" /></div>';
 						html += "<div style='flex:1;'>" + key++ + "</div>";
 						html += "<div style='flex:1;'>" + key++ + "</div>";
-						html += "<div style='flex:2;'>" + iterator.name + "</div>";
 						html += "<div style='flex:2;'>";
 						html += "<div style='flex:2;'>";
-						if (iterator.username == getCookie("username")) {
+						html += "<guide url='../channal/card.php' gid='" + iterator.id + "'>";
+						html += iterator.name;
+						html += "</guide>";
+						html += "</div>";
+						html += "<div style='flex:2;'>";
+						if (parseInt(iterator.power) == 30) {
 							html += gLocal.gui.your;
 							html += gLocal.gui.your;
 						} else {
 						} else {
+							html += "<guide url='../ucenter/card.php' gid='" + iterator.owner + "'>";
 							html += iterator.nickname;
 							html += iterator.nickname;
+							html += "</guide>";
 						}
 						}
 
 
 						html += "</div>";
 						html += "</div>";
@@ -67,16 +74,51 @@ function my_channal_list() {
 						}
 						}
 						//render_status(iterator.status) +
 						//render_status(iterator.status) +
 						html += "</div>";
 						html += "</div>";
-						html +=
-							"<div style='flex:1;'><a href='../channal/my_channal_edit.php?id=" +
-							iterator.id +
-							"'>" +
-							gLocal.gui.edit +
-							"</a></div>";
-						html += "<div style='flex:1;'></div>";
+
+						switch (parseInt(iterator.power)) {
+							case 10:
+								html += "<div style='flex:1;'>";
+								html += gLocal.gui.read_only;
+								html += "</div>";
+								html += "<div style='flex:1;'>";
+								html += "</div>";
+								break;
+							case 20:
+								html += "<div style='flex:1;'>";
+								html += gLocal.gui.write;
+								html += "</div>";
+								html += "<div style='flex:1;'>";
+								html +=
+									"<a href='../channal/my_channal_edit.php?id=" +
+									iterator.id +
+									"'>" +
+									gLocal.gui.edit +
+									"</a>";
+								html += "</div>";
+								break;
+							case 30:
+								html += "<div style='flex:1;'>";
+								html += gLocal.gui.owner;
+								html += "</div>";
+								html += "<div style='flex:1;'>";
+								html +=
+									"<a href='../channal/my_channal_edit.php?id=" +
+									iterator.id +
+									"'>" +
+									gLocal.gui.edit +
+									"</a>";
+								html += " <a onclick=\"channel_share('" + iterator.id + "')\">Share</a>";
+								html += "</div>";
+
+								break;
+							default:
+								break;
+						}
+
 						html += "</div>";
 						html += "</div>";
 					}
 					}
 					$("#my_channal_list").html(html);
 					$("#my_channal_list").html(html);
+					guide_init();
 				} catch (e) {
 				} catch (e) {
 					console.error(e);
 					console.error(e);
 				}
 				}
@@ -87,6 +129,9 @@ function my_channal_list() {
 	);
 	);
 }
 }
 
 
+function channel_share(id) {
+	share_win.show("../share/share.php?id=" + id + "&type=2");
+}
 /*
 /*
 编辑channel信息
 编辑channel信息
 */
 */
@@ -175,14 +220,20 @@ function my_channal_edit(id) {
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
-
+					/*
+					旧的channel分享方式 删除
 					html += "<div id='coop_div' style='padding:5px;position: relative;'>";
 					html += "<div id='coop_div' style='padding:5px;position: relative;'>";
-					html += "<h2>"+gLocal.gui.cooperators+"</h2>";
+					html += "<h2>" + gLocal.gui.cooperators + "</h2>";
 
 
-					html += "<button onclick='add_coop_user()'>"+gLocal.gui.add+gLocal.gui.cooperators+"</button>";
+					html +=
+						"<button onclick='add_coop_user()'>" + gLocal.gui.add + gLocal.gui.cooperators + "</button>";
 					html += "<div id='add_coop_user_dlg' class='float_dlg' style='left: 0;'></div>";
 					html += "<div id='add_coop_user_dlg' class='float_dlg' style='left: 0;'></div>";
 
 
-					html += "<button onclick='add_coop_group()' >"+gLocal.gui.add+gLocal.gui.cooperate_group+"</button>";
+					html +=
+						"<button onclick='add_coop_group()' >" +
+						gLocal.gui.add +
+						gLocal.gui.cooperate_group +
+						"</button>";
 					html += "<div id='add_coop_group_dlg' class='float_dlg' style='left: 0;'></div>";
 					html += "<div id='add_coop_group_dlg' class='float_dlg' style='left: 0;'></div>";
 					html += "<div id='coop_inner' >";
 					html += "<div id='coop_inner' >";
 					if (typeof result.coop == "undefined" || result.coop.length == 0) {
 					if (typeof result.coop == "undefined" || result.coop.length == 0) {
@@ -191,7 +242,7 @@ function my_channal_edit(id) {
 						for (const coop of result.coop) {
 						for (const coop of result.coop) {
 							html += '<div class="file_list_row" style="padding:5px;">';
 							html += '<div class="file_list_row" style="padding:5px;">';
 							if (coop.type == 0) {
 							if (coop.type == 0) {
-								html += '<div style="flex:1;">'+gLocal.gui.personal+'</div>';
+								html += '<div style="flex:1;">' + gLocal.gui.personal + "</div>";
 								html += "<div style='flex:3;'>" + coop.user_name.nickname + "</div>";
 								html += "<div style='flex:3;'>" + coop.user_name.nickname + "</div>";
 							} else {
 							} else {
 								html += '<div style="flex:1;">' + gLocal.gui.group + "</div>";
 								html += '<div style="flex:1;">' + gLocal.gui.group + "</div>";
@@ -200,14 +251,14 @@ function my_channal_edit(id) {
 
 
 							html += "<div style='flex:3;'>" + coop.power + "</div>";
 							html += "<div style='flex:3;'>" + coop.power + "</div>";
 							html += "<div class='hover_button' style='flex:3;'>";
 							html += "<div class='hover_button' style='flex:3;'>";
-							html += "<button>"+gLocal.gui.remove+"</button>";
+							html += "<button>" + gLocal.gui.remove + "</button>";
 							html += "</div>";
 							html += "</div>";
 							html += "</div>";
 							html += "</div>";
 						}
 						}
 					}
 					}
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
 					html += "</div>";
-
+*/
 					$("#channal_info").html(html);
 					$("#channal_info").html(html);
 					user_select_dlg_init("add_coop_user_dlg");
 					user_select_dlg_init("add_coop_user_dlg");
 					tran_lang_select_init("channal_lang_select");
 					tran_lang_select_init("channal_lang_select");

+ 86 - 10
app/channal/function.php

@@ -1,26 +1,102 @@
 <?php
 <?php
 require_once "../path.php";
 require_once "../path.php";
-class Channal
+require_once "../share/function.php";
+require_once "../db/table.php";
+
+class Channal extends Table
 {
 {
-    public $dbh;
-    public function __construct() {
-        $dns = ""._FILE_DB_CHANNAL_;
-        $this->dbh = new PDO($dns, "", "",array(PDO::ATTR_PERSISTENT=>true));
-        $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);  
+    function __construct($redis=false) {
+		parent::__construct(_FILE_DB_CHANNAL_, "channal", "", "",$redis);
     }
     }
 
 
     public function getChannal($id){
     public function getChannal($id){
         $query = "SELECT * FROM channal WHERE id= ? ";
         $query = "SELECT * FROM channal WHERE id= ? ";
         $stmt = $this->dbh->prepare($query);
         $stmt = $this->dbh->prepare($query);
         $stmt->execute(array($id));
         $stmt->execute(array($id));
-        $channal = $stmt->fetchAll(PDO::FETCH_ASSOC);
-        if(count($channal)>0){
-            return $channal[0];
+        $channal = $stmt->fetch(PDO::FETCH_ASSOC);
+        if($channal){
+            return $channal;
         }
         }
         else{
         else{
             return false;
             return false;
         }
         }
-    }
+	}
+	public function getTitle($id)
+	{
+		if (isset($id)) {
+			$query = "SELECT name FROM channal  WHERE id = ? ";
+			$stmt = $this->dbh->prepare($query);
+			$stmt->execute(array($id));
+			$channal = $stmt->fetch(PDO::FETCH_ASSOC);
+			if ($channel) {
+				return $channel["name"];
+			} else {
+				return "";
+			}
+		} else {
+			return "";
+		}
+	}
+	public function insert($data){
+
+	}
+	public function update($data){
+
+	}
+	public function delete($data){
+
+	}
+	public function getPower($id){
+		#查询用户对此channel是否有权限		
+		if(isset($_COOKIE["userid"])){
+			$userId = $_COOKIE["userid"];
+		}
+		else{
+			$userId='0';
+		}
+		if($this->redis!==false){
+			$power = $this->redis->hGet("power://channel/".$id,$userId);
+			if($power!==FALSE){
+				return $power;
+			}
+		}
+		$channelPower = 0;
+		$query = "SELECT owner,status FROM channal WHERE id=? and status>0 ";
+		$stmt = $this->dbh->prepare($query);
+		$stmt->execute(array($id));
+		$channel = $stmt->fetch(PDO::FETCH_ASSOC);
+		if($channel){
+			if(!isset($_COOKIE["userid"])  ){
+				#未登录用户
+				if($channel["status"]==30){
+					#全网公开有建议权限
+					return 10;
+				}
+				else{
+					#其他状态没有任何权限
+					return 0;
+				}
+				
+			}
+			if($channel["owner"]==$_COOKIE["userid"]){
+				return 30;
+			}
+			else if($channel["status"]>=30){
+				#全网公开的 可以提交pr
+				$channelPower = 10;
+			}
+		}
+		#查询共享权限,如果共享权限更大,覆盖上面的的
+		$sharePower = share_get_res_power($_COOKIE["userid"],$id);
+		if($sharePower>$channelPower){
+			$channelPower=$sharePower;
+		}
+		if($this->redis){
+			$this->redis->hSet("power://channel/".$id,$_COOKIE["userid"],$channelPower);
+		}
+		
+		return $channelPower;
+	}
 
 
 }
 }
 
 

+ 27 - 21
app/channal/get.php

@@ -4,12 +4,14 @@
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../ucenter/function.php';
 require_once '../ucenter/function.php';
+require_once '../share/function.php';
 
 
 if(!isset($_COOKIE["userid"])){
 if(!isset($_COOKIE["userid"])){
 	echo json_encode(array(), JSON_UNESCAPED_UNICODE);
 	echo json_encode(array(), JSON_UNESCAPED_UNICODE);
 	exit;
 	exit;
 }
 }
 
 
+/*
 # 找我加入的群
 # 找我加入的群
 PDO_Connect(""._FILE_DB_GROUP_);
 PDO_Connect(""._FILE_DB_GROUP_);
 $query = "SELECT group_id from group_member where user_id = ?  limit 0,100";
 $query = "SELECT group_id from group_member where user_id = ?  limit 0,100";
@@ -20,40 +22,44 @@ foreach ($my_group as $key => $value) {
 	# code...
 	# code...
 	$userList[]=$value["group_id"];
 	$userList[]=$value["group_id"];
 }
 }
+*/
+#查重复
+$channelList = array();
 
 
 //找自己的
 //找自己的
 PDO_Connect(""._FILE_DB_CHANNAL_);
 PDO_Connect(""._FILE_DB_CHANNAL_);
-$query = "SELECT * from channal where owner = ?  limit 0,100";
+$query = "SELECT id,owner,name,status,lang FROM channal WHERE owner = ?  LIMIT 0,100";
 $Fetch_my = PDO_FetchAll($query,array($_COOKIE["userid"]));
 $Fetch_my = PDO_FetchAll($query,array($_COOKIE["userid"]));
 
 
-$place_holders = implode(',', array_fill(0, count($userList), '?'));
+foreach ($Fetch_my as $key => $value) {
+	# code...
+	$channelList[$value["id"]]=array("id"=>$value["id"],"owner"=>$value["owner"],"name"=>$value["name"],"power"=>30,"status"=>$value["status"],"lang"=>$value["lang"]);
+}
+
 # 找协作的
 # 找协作的
+$coop_channal =  share_res_list_get($_COOKIE["userid"],2);
 $Fetch_coop = array();
 $Fetch_coop = array();
-$query = "SELECT channal_id FROM cooperation WHERE  user_id IN ($place_holders) ";
-$coop_channal = PDO_FetchAll($query,$userList);
-if(count($coop_channal)>0){
-    foreach ($coop_channal as $key => $value) {
-        # code...
-        $channal[]=$value["channal_id"];
-    }
-    /*  创建一个填充了和params相同数量占位符的字符串 */
-    $place_holders = implode(',', array_fill(0, count($channal), '?'));
-    $query = "SELECT * FROM channal WHERE id IN ($place_holders) order by owner";
-    $Fetch_coop = PDO_FetchAll($query,$channal);
+foreach ($coop_channal as $key => $value) {
+	# return res_id,res_type,power res_title  res_owner_id
+	if(isset($channelList[$value["res_id"]])){
+		if($channelList[$value["res_id"]]["power"]<(int)$value["power"]){
+			$channelList[$value["res_id"]]["power"]=(int)$value["power"];
+		}
+	}
+	else{
+		$channelList[$value["res_id"]]=array("id"=>$value["res_id"],"owner"=>$value["res_owner_id"],"name"=>$value["res_title"],"power"=>(int)$value["power"],"status"=>(int)$value["status"],"lang"=>(int)$value["lang"]);
+	}
 }
 }
-$all = array_merge_recursive($Fetch_my,$Fetch_coop);
-
-
 
 
 $_userinfo = new UserInfo();
 $_userinfo = new UserInfo();
 
 
 $output = array();
 $output = array();
-foreach ($all as $key => $value) {
+foreach ($channelList as $key => $value) {
     # code...
     # code...
-    $new = $value;
-    $name = $_userinfo->getName($value["owner"]);
-    $new["username"] = $name["username"];
-	$new["nickname"] = $name["nickname"];
+	$new = $value;
+	$name = $_userinfo->getName($value["owner"]);	
+	$new["username"] = $name["username"];
+	$new["nickname"] = $name["nickname"];	
 	$new["count"] = 0;
 	$new["count"] = 0;
     $new["all"] = 1;
     $new["all"] = 1;
     $output[]=$new;
     $output[]=$new;

+ 3 - 0
app/channal/my_channal_index.php

@@ -66,6 +66,9 @@ require_once '../studio/index_head.php';
 		
 		
 	</div>
 	</div>
 	
 	
+<div class="modal_win_bg">
+	<div id="share_win" class="iframe_container"></div>
+</div>
 
 
 <?php
 <?php
 require_once '../studio/index_foot.php';
 require_once '../studio/index_foot.php';

+ 13 - 20
app/channal/my_channal_post.php

@@ -2,31 +2,18 @@
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
+require_once '../channal/function.php';
+require_once '../redis/function.php';
 require_once '../hostsetting/function.php';
 require_once '../hostsetting/function.php';
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
 
 
 #先查询对此channal是否有权限修改
 #先查询对此channal是否有权限修改
-   PDO_Connect(""._FILE_DB_CHANNAL_);
+PDO_Connect(_FILE_DB_CHANNAL_);
 $cooperation = 0;
 $cooperation = 0;
 if(isset($_POST["id"])){
 if(isset($_POST["id"])){
-    $query = "SELECT owner FROM channal WHERE id=?";
-    $fetch = PDO_FetchOne($query,array($_POST["id"]));
-    if($fetch && $fetch==$_COOKIE["userid"]){
-        #自己的channal
-        $cooperation = 1;
-    }
-    else{
-        $query = "SELECT count(*) FROM cooperation WHERE channal_id= ? and user_id=? ";
-        $fetch = PDO_FetchOne($query,array($_POST["id"],$_COOKIE["userid"]));
-        if($fetch>0){
-            #有协作权限
-            $cooperation = 1;
-        }
-        else{
-            #无协作权限
-            $cooperation = 0;
-        }
-    }
+	$redis = redis_connect();
+	$channel = new Channal($redis);
+	$channelPower = $channel->getPower($_POST["id"]);
 }
 }
 else{
 else{
     $respond["status"] = 1;
     $respond["status"] = 1;
@@ -34,13 +21,14 @@ else{
     echo json_encode($respond, JSON_UNESCAPED_UNICODE);
     echo json_encode($respond, JSON_UNESCAPED_UNICODE);
     exit;
     exit;
 }
 }
-if($cooperation==0){
+if($channelPower<30){
     $respond["status"] = 1;
     $respond["status"] = 1;
     $respond["message"] = 'error 无修改权限';
     $respond["message"] = 'error 无修改权限';
     echo json_encode($respond, JSON_UNESCAPED_UNICODE);
     echo json_encode($respond, JSON_UNESCAPED_UNICODE);
     exit;
     exit;
 }
 }
 
 
+$channelOldInfo = $channel->getChannal($_POST["id"]);
 
 
 $query="UPDATE channal SET name = ? ,  summary = ?,  status = ? , lang = ? , receive_time= ?  , modify_time= ?   where  id = ?  ";
 $query="UPDATE channal SET name = ? ,  summary = ?,  status = ? , lang = ? , receive_time= ?  , modify_time= ?   where  id = ?  ";
 $sth = $PDO->prepare($query);
 $sth = $PDO->prepare($query);
@@ -52,6 +40,11 @@ if (!$sth || ($sth && $sth->errorCode() != 0)) {
 	$respond['message']=$error[2];
 	$respond['message']=$error[2];
 }
 }
 else{
 else{
+	if($redis){
+		if($channelOldInfo["status"]!=$_POST["status"]){
+			$redis->del("power://channel/".$_POST["id"]);
+		}
+	}
     // 设置 句子库和逐词译库可见性
     // 设置 句子库和逐词译库可见性
     PDO_Connect(""._FILE_DB_SENTENCE_);
     PDO_Connect(""._FILE_DB_SENTENCE_);
     $query="UPDATE sentence SET language = ?  , status = ? where  channal = ?  ";
     $query="UPDATE sentence SET language = ?  , status = ? where  channal = ?  ";

+ 2 - 1
app/channal/my_channal_put.php

@@ -1,11 +1,12 @@
 <?php
 <?php
+#新建channel
 require_once "../path.php";
 require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
 require_once '../hostsetting/function.php';
 require_once '../hostsetting/function.php';
 $respond=array("status"=>0,"message"=>"");
 $respond=array("status"=>0,"message"=>"");
 if(isset($_COOKIE["userid"])){
 if(isset($_COOKIE["userid"])){
-	PDO_Connect(""._FILE_DB_CHANNAL_);
+	PDO_Connect(_FILE_DB_CHANNAL_);
 	$query="INSERT INTO channal ( id,  owner  , name  , summary ,  status  , lang, create_time , modify_time , receive_time   )  VALUES  ( ? , ? , ? , ? , ? , ? , ? , ? , ?  ) ";
 	$query="INSERT INTO channal ( id,  owner  , name  , summary ,  status  , lang, create_time , modify_time , receive_time   )  VALUES  ( ? , ? , ? , ? , ? , ? , ? , ? , ?  ) ";
 	$sth = $PDO->prepare($query);
 	$sth = $PDO->prepare($query);
 	$sth->execute(array(UUID::v4() , $_COOKIE["userid"] , $_POST["name"] , "" , $_POST["status"] ,$_POST["lang"]  ,  mTime() ,  mTime() , mTime() ));
 	$sth->execute(array(UUID::v4() , $_COOKIE["userid"] , $_POST["name"] , "" , $_POST["status"] ,$_POST["lang"]  ,  mTime() ,  mTime() , mTime() ));

+ 34 - 0
app/collect/card.php

@@ -0,0 +1,34 @@
+<?php
+//
+
+require_once "../path.php";
+require_once "../public/_pdo.php";
+require_once '../ucenter/function.php';
+
+
+if (isset($_GET["id"])) {
+	$output["id"]=$_GET["id"];
+	PDO_Connect( _FILE_DB_USER_ARTICLE_);
+	$query = "SELECT title,create_time,owner,summary FROM collect  WHERE id = ? ";
+	$result = PDO_FetchRow($query, array($_GET["id"]));
+	$strData="";
+	if ($result) {
+		
+		$_userinfo = new UserInfo();
+		$name = $_userinfo->getName($result["owner"]);
+
+		$strData .= "<div>标题:".$result["title"]."</div>";
+		$strData .=  "<div>创建人:".$name["nickname"]."</div>";
+		$strData .=  "<div>创建时间:".date("Y/m/d",$result["create_time"]/1000)."</div>";
+		$strData .=  "<div>简介:".$result["summary"]."</div>";
+	} else {
+		$strData .=  "unkow";
+	}
+	$output["data"] = $strData;
+} else {
+	$output["id"]=0;
+	$output["data"] = "unkow";
+}
+echo json_encode($output, JSON_UNESCAPED_UNICODE);
+
+?>

+ 197 - 3
app/collect/function.php

@@ -1,14 +1,20 @@
 <?php
 <?php
 require_once '../path.php';
 require_once '../path.php';
+require_once "../share/function.php";
+require_once "../db/table.php";
+require_once "../article/function.php";
 
 
 class CollectInfo
 class CollectInfo
 {
 {
     private $dbh;
     private $dbh;
     private $buffer;
     private $buffer;
-    public function __construct() {
+	private $_redis;
+	private $errorMsg;
+    public function __construct($redis=false) {
         $dns = ""._FILE_DB_USER_ARTICLE_;
         $dns = ""._FILE_DB_USER_ARTICLE_;
         $this->dbh = new PDO($dns, "", "",array(PDO::ATTR_PERSISTENT=>true));
         $this->dbh = new PDO($dns, "", "",array(PDO::ATTR_PERSISTENT=>true));
         $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);  
         $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);  
+		$this->_redis=$redis;
         $buffer = array();
         $buffer = array();
     }
     }
 
 
@@ -20,7 +26,7 @@ class CollectInfo
             return $buffer[$id];
             return $buffer[$id];
         }
         }
         if($this->dbh){
         if($this->dbh){
-            $query = "SELECT id,title FROM collect WHERE id= ? limit 0,10";
+            $query = "SELECT id,title,owner,status,lang,article_list FROM collect WHERE id= ?";
             $stmt = $this->dbh->prepare($query);
             $stmt = $this->dbh->prepare($query);
             $stmt->execute(array($id));
             $stmt->execute(array($id));
 			$collect = $stmt->fetch(PDO::FETCH_ASSOC);
 			$collect = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -31,12 +37,200 @@ class CollectInfo
             else{
             else{
                 $buffer[$id] =false;
                 $buffer[$id] =false;
                 return $buffer[$id];
                 return $buffer[$id];
-            }            
+            }
         }
         }
         else{
         else{
             $buffer[$id] = false;
             $buffer[$id] = false;
             return $buffer[$id];
             return $buffer[$id];
         }
         }
     }
     }
+
+	public function getArticleList($id){
+        if(empty($id)){
+            return array();
+        }
+        if($this->dbh){
+            $query = "SELECT article_id FROM article_list WHERE collect_id= ? limit 0,1000";
+            $stmt = $this->dbh->prepare($query);
+            $stmt->execute(array($id));
+			$article_list = $stmt->fetchAll(PDO::FETCH_ASSOC);
+			$output=array();
+			foreach ($article_list as $key => $value) {
+				# code...
+				$output[]=$value["article_id"];
+			}
+			return $output;
+        }
+        else{
+            return array();
+        }
+    }
+
+	public function getPower($id){
+		#查询用户对此是否有权限	
+		if(isset($_COOKIE["userid"])){
+			$userId = $_COOKIE["userid"];
+		}
+		else{
+			$userId='0';
+		}
+		if($this->_redis!==false){
+			$power = $this->_redis->hGet("power://collection/".$id,$userId);
+			if($power!==FALSE){
+				return $power;
+			}
+		}
+		$iPower = 0;
+		$query = "SELECT owner,status FROM collect WHERE id=?  ";
+		$stmt = $this->dbh->prepare($query);
+		$stmt->execute(array($id));
+		$result = $stmt->fetch(PDO::FETCH_ASSOC);
+		if($result){
+			if(!isset($_COOKIE["userid"])){
+				#未登录用户
+				if($result["status"]==30){
+					#全网公开有读取和建议权限
+					return 10;
+				}
+				else{
+					#其他状态没有任何权限
+					return 0;
+				}
+			}
+			else{
+				if($result["owner"]==$_COOKIE["userid"]){
+					#自己的
+					return 30;
+				}
+				else if($result["status"]>=30){
+					#全网公开的 可以提交pr
+					$iPower = 10;
+				}			
+			}
+			#查询共享权限,如果共享权限更大,覆盖上面的的
+			$sharePower = share_get_res_power($userId,$id);
+			if($sharePower>$iPower){
+				$iPower=$sharePower;
+			}
+			$this->_redis->hSet("power://collection/".$id,$userId,$iPower);			
+		}
+		return $iPower;
+	}
+
+	public function update($arrData){
+		/* 修改现有数据 */
+	
+		if (count($arrData) > 0) {
+			$this->dbh->beginTransaction();
+			$query="UPDATE collect SET title = ? , subtitle = ? , summary = ?, article_list = ?  ,  status = ? , lang = ? , receive_time= ?  , modify_time= ?   where  id = ?  ";
+			$sth = $this->dbh->prepare($query);
+			foreach ($arrData as $data) {
+				$sth->execute(array($data["title"] , $data["subtitle"] ,$data["summary"], $data["article_list"] , $data["status"] , $data["lang"] ,  mTime() , mTime() , $data["id"]));
+			}
+			$this->dbh->commit();
+		
+			if (!$sth || ($sth && $sth->errorCode() != 0)) {
+				/*  识别错误且回滚更改  */
+				$this->dbh->rollBack();
+				$error = $this->dbh->errorInfo();
+				$this->errorMsg = $error[2];
+				return false;
+			} else {
+				#没错误 添加log 
+				$this->errorMsg = "";
+				//更新列表
+				$ArticleList = new ArticleList($this->_redis);
+				foreach ($arrData as $data) {
+					$arrList = json_decode($data["article_list"],true);
+					$ArticleList->upgrade($data["id"],$arrList);
+				}
+				return true;
+			}
+		}
+		else{
+			$this->errorMsg = "";
+			return true;
+		}
+	}
+
+	public function insert($arrData){
+		/* 插入新数据 */
+		$respond=array("status"=>0,"message"=>"");
+		if (count($arrData) > 0) {
+			$this->dbh->beginTransaction();
+			$query="INSERT INTO collect ( id,  title  , subtitle  , summary , article_list , owner, lang  , status  , create_time , modify_time , receive_time   )  VALUES  ( ? , ? , ? , ?  , ? , ? , ? , ? , ? , ? , ? ) ";
+			$sth = $this->dbh->prepare($query);
+			$newDataList=array();
+			foreach ($arrData as $data) {
+				$newData = array();
+				if(isset($data["title"]) && !empty($data["title"])){
+					$newData["title"]=$data["title"];
+					if(isset($data["id"])){
+						$newData["id"]=$data["id"];
+					}
+					else{
+						$newData["id"]=UUID::v4();
+					}
+					if(isset($data["subtitle"])){
+						$newData["subtitle"]=$data["subtitle"];
+					}
+					else{
+						$newData["subtitle"]="";
+					}
+					if(isset($data["summary"])){
+						$newData["summary"]=$data["summary"];
+					}
+					else{
+						$newData["summary"]="";
+					}	
+					if(isset($data["article_list"])){
+						$newData["article_list"]=$data["article_list"];
+					}
+					else{
+						$newData["article_list"]="";
+					}		
+					if(isset($data["lang"])){
+						$newData["lang"]=$data["lang"];
+					}
+					else{
+						$newData["lang"]="";
+					}
+					if(isset($data["status"])){
+						$newData["status"]=$data["status"];
+					}
+					else{
+						$newData["status"]="1";
+					}
+					$newDataList[]=$newData;
+					$sth->execute(array($newData["id"] , $newData["title"] , $newData["subtitle"] ,$newData["summary"], $newData["article_list"] ,  $_COOKIE["userid"] , $newData["lang"] , $newData["status"] , mTime() ,  mTime() , mTime() ));				
+				}
+				else{
+					$this->errorMsg="标题不能为空";
+				}
+			}
+			$this->dbh->commit();
+	
+			if (!$sth || ($sth && $sth->errorCode() != 0)) {
+				/*  识别错误且回滚更改  */
+				$this->dbh->rollBack();
+				$error = $this->dbh->errorInfo();
+				$this->errorMsg = $error[2];
+				return false;
+			} else {
+				$this->errorMsg = "";
+				//更新列表
+				$ArticleList = new ArticleList($this->_redis);
+				foreach ($newDataList as $data) {
+					$arrList = json_decode($data["article_list"],true);
+					$ArticleList->upgrade($data["id"],$arrList);
+				}
+				return true;
+			}
+		}
+		else{
+			$this->errorMsg = "";
+			return true;
+		}
+	}
 }
 }
 ?>
 ?>

+ 2 - 2
app/collect/list.php

@@ -23,8 +23,8 @@ require_once '../ucenter/function.php';
     else{
     else{
         $begin = 0;
         $begin = 0;
     }
     }
-    PDO_Connect(""._FILE_DB_USER_ARTICLE_);
-    $query = "SELECT * FROM collect  where  1 ";
+    PDO_Connect(_FILE_DB_USER_ARTICLE_);
+    $query = "SELECT * FROM collect  where  status>=30 ";
     if(isset($_GET["orderby"])){
     if(isset($_GET["orderby"])){
         switch ($_GET["orderby"]) {
         switch ($_GET["orderby"]) {
             case 'like':
             case 'like':

+ 61 - 0
app/commit/commit.css

@@ -0,0 +1,61 @@
+.commit_compare {
+	display: flex;
+	padding: 5px;
+	line-height: 1.8em;
+	border-bottom: 1px solid var(--border-line-color);
+}
+.commit_compare .pali {
+	flex: 3;
+}
+.commit_compare .src_text {
+	flex: 3;
+}
+.commit_compare .dest_text {
+	flex: 3;
+}
+
+del {
+	color: red;
+}
+ins {
+	color: green;
+}
+
+.commit_head {
+	display: flex;
+	justify-content: space-around;
+	padding-bottom: 10px;
+	margin-bottom: 10px;
+	border-bottom: 1px solid var(--border-line-color);
+}
+.commit_step {
+	text-align: center;
+}
+.commit_step .num {
+	font-size: 25px;
+	font-weight: 700;
+	color: var(--btn-color);
+	width: 50px;
+	line-height: 45px;
+	text-align: center;
+	border: 2px solid var(--link-color);
+	border-radius: 99px;
+}
+.step1 > .commit_step_1 > .num {
+	border-color: var(--link-hover-color);
+	color: var(--main-color);
+}
+.step2 > .commit_step_1 > .num,
+.step2 > .commit_step_2 > .num {
+	border-color: var(--link-hover-color);
+	color: var(--main-color);
+}
+.step3 > .commit_step_1 > .num,
+.step3 > .commit_step_2 > .num,
+.step3 > .commit_step_3 > .num {
+	border-color: var(--link-hover-color);
+	color: var(--main-color);
+}
+.commit_win_inner {
+	padding: 1em;
+}

+ 356 - 0
app/commit/commit.js

@@ -0,0 +1,356 @@
+var _commit_data;
+var previewWin;
+var sentData;
+
+function commit_init(param) {
+	previewWin = model_win_init({ container: "model_win" });
+	_commit_data = param;
+
+	previewWin.show(commit_preview_render());
+}
+function commit_render_head(step) {
+	let html = "";
+	html += "<div class='commit_head step" + step + "'>";
+	html += "<div class='commit_step commit_step_1'><div class='num'>1</div><div>选择版本</div></div>";
+	html += "<div class='commit_step commit_step_2'><div class='num'>2</div><div>文本比对</div></div>";
+	html += "<div class='commit_step commit_step_3'><div class='num'>3</div><div>完成</div></div>";
+	html += "</div>";
+	return html;
+}
+function commit_render_channel_select() {
+	let html = "";
+	html += "<div class='commit_win_inner' >";
+	html += commit_render_head(1);
+	html += "<div style='display:flex;'>";
+
+	if (typeof _commit_data.src != "undefined") {
+		html += "<div>译文来源:";
+		let isFound = false;
+		for (const iterator of _my_channal) {
+			if (_commit_data.src == iterator.id) {
+				html += iterator.name;
+				isFound = true;
+				break;
+			}
+		}
+		if (!isFound) {
+			for (const iterator of _channalData) {
+				if (_commit_data.src == iterator.id) {
+					html += iterator.name;
+					isFound = true;
+					break;
+				}
+			}
+		}
+		if (!isFound) {
+			html += "无法找到Channel";
+		}
+		html += "</div>";
+	} else {
+		html += "<div>请选择译文来源";
+		html += "</div>";
+	}
+	html += "<div>➡目标译文:</div>";
+	html += "<div>";
+	html += "<select id='dest_channel' onchange='dest_change(this)'>";
+	if (typeof _commit_data.dest == "undefined") {
+		let lastDest = localStorage.getItem("commit_src_" + _commit_data.src);
+		if (typeof lastDest == "undefined") {
+			html += "<option value='' selected>请选择目标版本</option>";
+		} else {
+			_commit_data.dest = lastDest;
+		}
+	}
+	for (const iterator of _my_channal) {
+		if (iterator.status > 0 && _commit_data.src != iterator.id) {
+			html += "<option value='" + iterator.id + "' ";
+			if (_commit_data.dest == iterator.id) {
+				html += " selected ";
+			}
+			if (typeof _commit_data.src != "undefined" && _commit_data.src == iterator.id) {
+				html += "style:'display:none;' ";
+			}
+			html += " >" + iterator.name + "-";
+			if (iterator.power >= 30) {
+				html += gLocal.gui.your;
+			} else if (iterator.power >= 20) {
+				html += "可编辑";
+			} else if (iterator.power >= 10) {
+				html += "只读";
+			} else {
+				html += "停用";
+			}
+			html += "</option>";
+		}
+	}
+	html += "</select>";
+	html += "</div>";
+
+	html += "<div id='commit_preview'>";
+	if (typeof _commit_data.express != "undefined" && _commit_data.express == true) {
+		if (typeof _commit_data.sent != "undefined" && _commit_data.sent.length != 0) {
+			html += "<button onclick='commit_pull()'>✔</button>";
+		} else {
+			html += "没有句子数据";
+		}
+	} else {
+		html += "<button onclick='previewWin.show(commit_preview_render())'>文本比对</button>";
+	}
+
+	html += "</div>";
+	html += "</div>";
+
+	html += "</div>";
+	return html;
+}
+
+function commit_preview_render() {
+	let html = "";
+	html += "<div class='commit_win_inner'>";
+	html += commit_render_head(2);
+
+	if (
+		typeof _commit_data.src != "undefined" &&
+		_commit_data.src != null &&
+		_commit_data.src != "" &&
+		typeof _commit_data.dest != "undefined" &&
+		_commit_data.dest != null &&
+		_commit_data.dest != ""
+	) {
+		if (typeof _commit_data.sent == "undefined" || _commit_data.sent.length == 0) {
+			let sentList = new Array();
+			for (const iterator of _arrData) {
+				sentList.push(iterator.book + "-" + iterator.para + "-" + iterator.begin + "-" + iterator.end);
+			}
+			_commit_data.sent = sentList;
+		}
+
+		let arrSentInfo = new Array();
+		for (const iterator of _commit_data.sent) {
+			let id = com_guid();
+			arrSentInfo.push({ id: id, data: iterator });
+		}
+
+		if (arrSentInfo.length > 0) {
+			let setting = new Object();
+			setting.lang = "";
+			setting.channal = _commit_data.src + "," + _commit_data.dest;
+			$.post(
+				"../term/note.php",
+				{
+					setting: JSON.stringify(setting),
+					data: JSON.stringify(arrSentInfo),
+				},
+				function (data, status) {
+					if (status == "success") {
+						try {
+							sentData = JSON.parse(data);
+						} catch (e) {}
+
+						previewWin.show(commit_render_comp(0));
+					}
+				}
+			);
+			html += "加载中 请稍等……";
+			html += "</div>";
+			return html;
+		} else {
+			html += "没有句子被选择";
+			html += "</div>";
+			return html;
+		}
+	} else {
+		return commit_render_channel_select();
+	}
+}
+function commit_compare_mode_change(obj) {
+	previewWin.show(commit_render_comp(parseInt($(obj).val())));
+}
+function commit_render_comp(mode) {
+	let html = "";
+	html += "<div class='commit_win_inner'>";
+	html += commit_render_head(2);
+	html += "<div>";
+	html += "<button onclick='previewWin.show(commit_render_channel_select())'>返回</button>";
+	html += "<button onclick='commit_pull()'>推送</button>";
+	html += "<button onclick='commit_close()'>放弃</button>";
+	html += "</div>";
+	html += "<div class='commit_compare'>";
+	html += "<div >";
+	html += "<select onchange='commit_compare_mode_change(this)'>";
+	let compareMode = [
+		{ id: 0, string: "自动" },
+		{ id: 1, string: "全选" },
+		{ id: 2, string: "全不选" },
+	];
+	for (const iterator of compareMode) {
+		html += "<option value='" + iterator.id + "' ";
+		if (mode == iterator.id) {
+			html += " selected ";
+		}
+		html += ">" + iterator.string + "</option>";
+	}
+	html += "</select>";
+	html += "</div>";
+	html += "<div class='pali'>巴利原文</div>";
+	html += "<div class='src_text'>当前版本:" + channal_getById(_commit_data.src).name + "</div>";
+	html += "<div class='dest_text'>推送到:" + channal_getById(_commit_data.dest).name + "</div>";
+	html += "</div>";
+
+	let textCount = 0;
+	let sentIndex = 0;
+	for (let iterator of sentData) {
+		iterator.checked = false;
+		if (iterator.translation[0].text != iterator.translation[1].text) {
+			textCount++;
+			if (iterator.translation[0].id != "") {
+				html += "<div class='commit_compare'>";
+				html += "<div >";
+				html += "<input class='sent_checkbox' index='" + sentIndex + "' type='checkbox' ";
+				switch (mode) {
+					case 0:
+						if (iterator.translation[1].id == "") {
+							html += " checked ";
+							iterator.checked = true;
+						} else {
+							if (iterator.translation[0].update_time > iterator.translation[1].update_time) {
+								html += " checked ";
+								iterator.checked = true;
+							}
+						}
+						break;
+					case 1:
+						html += " checked ";
+						iterator.checked = true;
+						break;
+					case 2:
+						break;
+					default:
+						break;
+				}
+				html += " sent_id='" + iterator.pali_sent_id;
+				html += "' onclick='commit_sent_select(this)' /></div>";
+				html += "<div class='pali'>" + iterator.palitext + "</div>";
+				html += "<div class='src_text'>";
+				html += iterator.translation[0].text;
+				html += "</div>";
+				html += "<div ";
+				html += "channel='" + _commit_data.dest + "'";
+				html += "sent_id='" + iterator.pali_sent_id + "'";
+				html += " class='dest_text'>";
+				switch (mode) {
+					case 0:
+						if (iterator.translation[1].id == "") {
+							html += "<ins>" + iterator.translation[0].text + "</ins>";
+						} else {
+							if (iterator.translation[0].update_time > iterator.translation[1].update_time) {
+								html += "<del>" + iterator.translation[1].text + "</del><br>";
+								html += "<ins>" + iterator.translation[0].text + "</ins>";
+							} else {
+								html += "[新]" + iterator.translation[1].text;
+							}
+						}
+						break;
+					case 1:
+						html += "<del>" + iterator.translation[1].text + "</del><br>";
+						html += "<ins>" + iterator.translation[0].text + "</ins>";
+						break;
+					case 2:
+						html += iterator.translation[1].text;
+						break;
+				}
+
+				html += "</div>";
+				html += "</div>";
+			}
+		}
+		sentIndex++;
+	}
+	if (textCount == 0) {
+		html += "译文全部相同,无需推送。";
+	}
+	html += "</div>";
+	return html;
+}
+
+function commit_sent_select(obj) {
+	let sent_id = $(obj).attr("sent_id");
+	for (let iterator of sentData) {
+		if (iterator.pali_sent_id == sent_id) {
+			let html = "";
+			iterator.checked = obj.checked;
+			if (obj.checked) {
+				if (iterator.translation[1].id != "") {
+					html += "<del>" + iterator.translation[1].text + "</del><br>";
+				}
+				html += "<ins>" + iterator.translation[0].text + "</ins>";
+			} else {
+				html += iterator.translation[1].text;
+			}
+			$(".dest_text[sent_id='" + sent_id + "']").html(html);
+		}
+	}
+}
+
+function commit_render_final(result) {
+	let html = "";
+	html += "<div class='commit_win_inner'>";
+	html += commit_render_head(3);
+	if (typeof result.update != "undefined") {
+		html += "<div>修改:" + result.update + "</div>";
+	}
+	if (typeof result.insert != "undefined") {
+		html += "<div>新增:" + result.insert + "</div>";
+	}
+	if (typeof result.pr != "undefined") {
+		html += "<div>提交修改建议:" + result.pr + "</div>";
+	}
+	html +=
+		"<div><a href='' onclick='window.reload()'>刷新页面</a>查看修改结果。<a onclick='previewWin.close()'>关闭</a></div>";
+	html += "</div>";
+	return html;
+}
+function commit_pull() {
+	localStorage.setItem("commit_src_" + _commit_data.src, _commit_data.dest);
+	let pullData = new Array();
+	for (const iterator of sentData) {
+		if (iterator.checked) {
+			for (const iterator of _arrData) {
+				pullData.push(iterator.book + "-" + iterator.para + "-" + iterator.begin + "-" + iterator.end);
+			}
+			_commit_data.sent = pullData;
+		}
+	}
+	if (pullData.length == 0) {
+		alert("没有数据被选择");
+		return;
+	}
+	$.post(
+		"../commit/commit.php",
+		{
+			data: JSON.stringify(_commit_data),
+		},
+		function (data, status) {
+			if (status == "success") {
+				let html = "";
+				try {
+					let result = JSON.parse(data);
+					if (result.status == 0) {
+						previewWin.show(commit_render_final(result));
+					} else {
+						alert(result.message);
+					}
+				} catch (e) {}
+			}
+		}
+	);
+}
+function commit_close() {}
+function dest_change(obj) {
+	_commit_data.dest = $(obj).val();
+	localStorage.setItem("commit_src_" + _commit_data.src, _commit_data.dest);
+}
+function src_change(obj) {
+	_commit_data.src = $(obj).val();
+	localStorage.setItem("commit_last_src", _commit_data.src);
+}

+ 145 - 0
app/commit/commit.php

@@ -0,0 +1,145 @@
+<?php 
+require_once "../path.php";
+require_once "../public/_pdo.php";
+require_once "../public/function.php";
+require_once "../channal/function.php";
+require_once "../redis/function.php";
+require_once "../share/function.php";
+require_once "../usent/function.php";
+
+$respond['message'] = "";
+$respond['status'] = 0;
+
+$_data = array();
+if (isset($_POST["data"])) {
+    $_data = json_decode($_POST["data"], true);
+} else {
+	$respond['message'] = "缺少输入数据";
+	$respond['status'] = 1;
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;
+}
+$channelInfo  = new Channal();
+$srcChannelPower = $channelInfo->getPower($_data["src"]);
+$destChannelPower = $channelInfo->getPower($_data["dest"]);
+
+
+if($srcChannelPower<10){
+	$respond['message'] = "源channel无权限";
+	$respond['status'] = 1;
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;		
+}
+if($destChannelPower<10){
+	$respond['message'] = "channel无权限";
+	$respond['status'] = 1;
+	echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+	exit;		
+}
+
+$db_trans_sent = new PDO(_FILE_DB_SENTENCE_, "", "", array(PDO::ATTR_PERSISTENT => true));
+$db_trans_sent->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+
+$query = "SELECT * FROM sentence WHERE book= ? AND paragraph= ? AND begin= ? AND end= ?  AND channal = ?  ";
+$stmt = $db_trans_sent->prepare($query);
+
+if($stmt){
+	$updateDate=array();
+	$insertData=array();
+	$prData=array();
+	$insertHistoray=array();
+	foreach ($_data["sent"] as $key => $value) {
+		# code...
+		$infoSrc = explode("-",$value);
+		$infoDest = $infoSrc;
+		$infoSrc[]=$_data["src"];
+		$infoDest[]=$_data["dest"];
+		$stmt->execute($infoSrc);
+		$fetchSrc = $stmt->fetch(PDO::FETCH_ASSOC);
+		if ($fetchSrc) {
+			# 有 源数据
+			$newData = $fetchSrc;
+			$newData["modify_time"]=mTime();
+			$newData["channal"]=$_data["dest"];
+			$newData["landmark"]="";
+			$stmt->execute($infoDest);
+			$fetchDest = $stmt->fetch(PDO::FETCH_ASSOC);
+			if ($fetchDest) {
+				#有目标数据,比较时间
+				$insert=false;
+				if(isset($_data["compare"]) && $_data["compare"]=="auto"){
+					if($fetchSrc["modify_time"]>$fetchDest["modify_time"]){
+						$insert = true;
+					}					
+				}
+				else{
+					$insert = true;
+				}
+				if($insert){
+					#新数据 更新
+					if($destChannelPower>=20){
+						#有权限 直接写入
+						$newData["id"]=$fetchDest["id"];
+						$updateDate[] = $newData;
+						$insertHistoray[] = $newData;
+					}
+					else{
+						#pr
+						$prData[] = $newData;
+					}					
+				}
+			}
+			else{
+				#没有目标数据新增
+				if($destChannelPower>=20){
+					#有写入权限 直接写入
+					$newData["id"] = UUID::v4();
+					$insertData[] = $newData;
+					$insertHistoray[] = $newData;
+				}
+				else{
+					#pr
+					$prData[] = $newData;
+				}
+			}
+		}
+	}
+	#到此,所有的数据已经准备好
+
+	$sentDb = new Sent_DB();
+
+	if($sentDb->update($updateDate)){
+		$respond['update'] = count($updateDate);
+	}
+	else{
+		$respond['message'] = $sentDb->getError();
+        $respond['status'] = 1;
+	}
+	if($sentDb->insert($insertData)){
+		$respond['insert'] = count($insertData);
+
+	}else{
+		$respond['message'] = $sentDb->getError();
+        $respond['status'] = 1;
+	}
+	if($sentDb->send_pr($prData)){
+		$respond['pr'] = count($prData);
+
+	}else{
+		$respond['message'] = $sentDb->getError();
+        $respond['status'] = 1;
+	}
+	if($sentDb->historay($insertHistoray)){
+		$respond['historay'] = count($insertHistoray);
+
+	}else{
+		$respond['message'] = $sentDb->getError();
+        $respond['status'] = 1;
+	}
+}
+else{
+	$respond['message'] = $db_trans_sent->errorInfo();
+	$respond['status'] = 1;
+}
+echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+?>

+ 16 - 0
app/db/table.php

@@ -0,0 +1,16 @@
+<?php
+class Table
+{
+    protected $dbh;
+	protected $table;
+    protected $redis;
+    function __construct($db,$table,$user="",$password="",$redis=false) {
+        $this->dbh = new PDO($db, $user, $password,array(PDO::ATTR_PERSISTENT=>true));
+        $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+		$this->redis = $redis;
+		$this->table = $table;
+
+    }
+}
+
+?>

+ 1 - 0
app/dict/comp_csv.php

@@ -59,6 +59,7 @@ while($word = $redis->hGet("pali://wordindex.hash",$start))
         foreach ($arrword as $oneword) {
         foreach ($arrword as $oneword) {
 			$result = array(); //全局变量,递归程序的输出容器
 			$result = array(); //全局变量,递归程序的输出容器
 			$min_result = 1;
 			$min_result = 1;
+			
 			if(mb_strlen($oneword)>35){
 			if(mb_strlen($oneword)>35){
 				mySplit2($oneword, 0, true, 0.8, 0.9, 0, true, false);
 				mySplit2($oneword, 0, true, 0.8, 0.9, 0, true, false);
 			}
 			}

+ 0 - 1
app/dict/css/style.css

@@ -305,7 +305,6 @@ input[type="submit"] {
 	margin: 2px 0;
 	margin: 2px 0;
 	line-height: 160%;
 	line-height: 160%;
 	font-weight: 400;
 	font-weight: 400;
-	display: inline-block;
 	font-style: normal;
 	font-style: normal;
 }
 }
 
 

+ 1 - 12
app/dict/css/style_mobile.css

@@ -122,7 +122,6 @@ body {
 	margin-left: 0.1em;
 	margin-left: 0.1em;
 	font-size: 120%;
 	font-size: 120%;
 	padding: 0.3em;
 	padding: 0.3em;
-	width:95%;
 }
 }
 
 
 #left_menu_button{
 #left_menu_button{
@@ -135,13 +134,9 @@ body {
 
 
 #right_bar{
 #right_bar{
 	display:none;
 	display:none;
-	position: fixed;
-	width: 100%;
 }
 }
 .search_toolbar{
 .search_toolbar{
 	padding:0.5em 0;
 	padding:0.5em 0;
-	position: fixed;
-	width: 100%;
 }
 }
 
 
 guide{
 guide{
@@ -150,10 +145,4 @@ guide{
 
 
 #search_info {
 #search_info {
     display: block;
     display: block;
-}
-#main_result{
-	margin-top: 5em;
-}
-.foot_div {
-	margin-top: 5em;
-}
+}

+ 1 - 1
app/dict/dict.js

@@ -21,7 +21,7 @@ function dict_search(word, autoSplit = true) {
 		localStorage.searchword = word + "," + oldHistory;
 		localStorage.searchword = word + "," + oldHistory;
 	}
 	}
 	word = standardize(word);
 	word = standardize(word);
-	word = com_getPaliReal(word);
+	//word = com_getPaliReal(word);
 
 
 	$.get(
 	$.get(
 		"dict_lookup.php",
 		"dict_lookup.php",

+ 79 - 19
app/dict/dict_lookup.php

@@ -18,8 +18,6 @@ $redis = redis_connect();
 global $count_return;
 global $count_return;
 $count_return = 0;
 $count_return = 0;
 
 
-_load_book_index();
-
 $word = mb_strtolower($_GET["word"], 'UTF-8');
 $word = mb_strtolower($_GET["word"], 'UTF-8');
 $org_word = $word;
 $org_word = $word;
 
 
@@ -49,6 +47,7 @@ $right_word_list = "";
 
 
         $Fetch = PDO_FetchAll($query, array($word));
         $Fetch = PDO_FetchAll($query, array($word));
         $iFetch = count($Fetch);
         $iFetch = count($Fetch);
+		echo "直接查询{$iFetch}<br>";
         $count_return += $iFetch;
         $count_return += $iFetch;
         if ($iFetch > 0) {
         if ($iFetch > 0) {
             for ($i = 0; $i < $iFetch; $i++) {
             for ($i = 0; $i < $iFetch; $i++) {
@@ -141,7 +140,7 @@ $right_word_list = "";
 		if (count($newWord) > 0) {
 		if (count($newWord) > 0) {
 			foreach ($newWord as $base => $grammar){
 			foreach ($newWord as $base => $grammar){
 				for ($row = 0; $row < count($p_ending); $row++) {
 				for ($row = 0; $row < count($p_ending); $row++) {
-					$len = mb_strlen($case[$row][1], "UTF-8");
+					$len = mb_strlen($p_ending[$row][1], "UTF-8");
 					$end = mb_substr($base, 0 - $len, null, "UTF-8");
 					$end = mb_substr($base, 0 - $len, null, "UTF-8");
 					if ($end == $p_ending[$row][1]) {
 					if ($end == $p_ending[$row][1]) {
 						$newbase = mb_substr($base, 0, mb_strlen($base, "UTF-8") - $len, "UTF-8") . $p_ending[$row][0];
 						$newbase = mb_substr($base, 0, mb_strlen($base, "UTF-8") - $len, "UTF-8") . $p_ending[$row][0];
@@ -171,14 +170,14 @@ $right_word_list = "";
 					
 					
 					$query = "SELECT dict.dict_id,dict.mean,info.shortname from " . _TABLE_DICT_REF_ . " LEFT JOIN info ON dict.dict_id = info.id where word = ? limit 0,30";
 					$query = "SELECT dict.dict_id,dict.mean,info.shortname from " . _TABLE_DICT_REF_ . " LEFT JOIN info ON dict.dict_id = info.id where word = ? limit 0,30";
 					$Fetch = PDO_FetchAll($query, array($x));
 					$Fetch = PDO_FetchAll($query, array($x));
-					$iFetch = count($Fetch);
+					$iFetch = count($Fetch);	
 					$count_return += $iFetch;
 					$count_return += $iFetch;
 					if ($iFetch > 0) {
 					if ($iFetch > 0) {
 						$base_list[] = $x;
 						$base_list[] = $x;
 						$dict_list_a[] = array("word_$x", $x);
 						$dict_list_a[] = array("word_$x", $x);
 						echo "<div class='pali_spell'><a name='word_$x'></a>" . $x . "</div>";
 						echo "<div class='pali_spell'><a name='word_$x'></a>" . $x . "</div>";
-						echo "<div style='color:gray;'>{$x}->{$x_value["parent"]}->{$word}</div>";
-						//语法信息
+						echo "<div style='color:gray;'>{$x}➡{$x_value["parent"]}➡{$word}</div>";
+						//替换为本地语法信息
 						foreach ($_local->grammastr as $gr) {
 						foreach ($_local->grammastr as $gr) {
 							$x_value['grammar'] = str_replace($gr->id, $gr->value, $x_value['grammar']);
 							$x_value['grammar'] = str_replace($gr->id, $gr->value, $x_value['grammar']);
 						}
 						}
@@ -210,6 +209,7 @@ $right_word_list = "";
 				echo "<a>{$value}</a> ";
 				echo "<a>{$value}</a> ";
 			}
 			}
 		}
 		}
+		echo "<a>查询内文</a>";
 		echo "</div>";
 		echo "</div>";
 		echo "<input type='hidden' id='word_count' value='{$count_return}' />";
 		echo "<input type='hidden' id='word_count' value='{$count_return}' />";
 
 
@@ -235,9 +235,9 @@ $right_word_list = "";
 		//拆复合词
 		//拆复合词
 		echo "<div id='auto_split'></div>";
 		echo "<div id='auto_split'></div>";
 
 
-/*
+
         //查内容
         //查内容
-        if ($count_return < 2) {
+        if ($count_return < 4) {
             $word1 = $org_word;
             $word1 = $org_word;
             $wordInMean = "%$org_word%";
             $wordInMean = "%$org_word%";
             echo "包含 $org_word 的:<br />";
             echo "包含 $org_word 的:<br />";
@@ -265,14 +265,18 @@ $right_word_list = "";
                     $end = mb_substr($newmean, $pos + mb_strlen($word1, "UTF-8"), null, "UTF-8");
                     $end = mb_substr($newmean, $pos + mb_strlen($word1, "UTF-8"), null, "UTF-8");
                     $heigh_light_mean = "$head<hl>$mid</hl>$end";
                     $heigh_light_mean = "$head<hl>$mid</hl>$end";
                     echo "<div class='dict_word'>";
                     echo "<div class='dict_word'>";
-                    echo "<div class='pali'>" . $Fetch[$i]["word"] . "</div>";
+                    echo "<div class='pali'><a href='index.php?key={$Fetch[$i]["word"]}'>" . $Fetch[$i]["word"] . "</a></div>";
                     echo "<div class='dict'>" . $Fetch[$i]["shortname"] . "</div>";
                     echo "<div class='dict'>" . $Fetch[$i]["shortname"] . "</div>";
-                    echo "<div class='mean'>" . $heigh_light_mean . "</div>";
+					echo "<div class='mean'>" . $heigh_light_mean . "</div>";
+					echo "<div><a href='index.php?key={$Fetch[$i]["word"]}&hightlight={$org_word}'>详情</a></div>";
                     echo "</div>";
                     echo "</div>";
                 }
                 }
             }
             }
-        }
-*/
+		}
+		else{
+
+		}
+
         echo "<div id='dictlist'>";
         echo "<div id='dictlist'>";
         foreach ($dict_list_a as $x_value) {
         foreach ($dict_list_a as $x_value) {
             if (substr($x_value[0], 0, 4) == "word") {
             if (substr($x_value[0], 0, 4) == "word") {
@@ -394,8 +398,9 @@ function lookup_user($word){
 	}
 	}
 	
 	
 	$iFetch = count($Fetch);
 	$iFetch = count($Fetch);
-	$count_return += $iFetch;
+	
 	if ($iFetch > 0) {
 	if ($iFetch > 0) {
+		$count_return++;
 		$userlist = array();
 		$userlist = array();
 		foreach ($Fetch as $value) {
 		foreach ($Fetch as $value) {
 			if (isset($userlist[$value["creator"]])) {
 			if (isset($userlist[$value["creator"]])) {
@@ -404,12 +409,19 @@ function lookup_user($word){
 				$userlist[$value["creator"]] = 1;
 				$userlist[$value["creator"]] = 1;
 			}
 			}
 			$userwordcase = $value["type"] . "#" . $value["gramma"];
 			$userwordcase = $value["type"] . "#" . $value["gramma"];
-			if (isset($userdict["{$userwordcase}"])) {
-				$userdict["{$userwordcase}"]["mean"] .= $value["mean"] . ";";
-				$userdict["{$userwordcase}"]["factors"] .= $value["factors"];
+			$parent = $value["parent"];
+			if(empty($parent)){
+				$parent = "_null_";
+			}
+			if (isset($userdict["{$parent}"])) {
+				$userdict["{$parent}"]["mean"] .= "$". $value["mean"] ;
+				$userdict["{$parent}"]["factors"] .= "@". $value["factors"];
+				$userdict["{$parent}"]["case"] .= "@".$userwordcase;
+
 			} else {
 			} else {
-				$userdict["{$userwordcase}"]["mean"] = $value["mean"];
-				$userdict["{$userwordcase}"]["factors"] = $value["factors"];
+				$userdict["{$parent}"]["mean"] = $value["mean"];
+				$userdict["{$parent}"]["factors"] = $value["factors"];
+				$userdict["{$parent}"]["case"] = $userwordcase;
 			}
 			}
 
 
 		}
 		}
@@ -417,8 +429,56 @@ function lookup_user($word){
 		$output .= "<div class='dict'>{$_local->gui->com_dict}</div><a name='net'></a>";
 		$output .= "<div class='dict'>{$_local->gui->com_dict}</div><a name='net'></a>";
 		$dict_list_a[] = array("net", $_local->gui->com_dict);
 		$dict_list_a[] = array("net", $_local->gui->com_dict);
 
 
+
 		foreach ($userdict as $key => $value) {
 		foreach ($userdict as $key => $value) {
-			$output .= "<div class='mean'>{$key}:{$value["mean"]}</div>";
+			#语法信息查重
+			$thiscase = array();
+			$strCase = "";
+			$arrCase = explode("@",$value["case"]);
+			foreach ($arrCase as  $case) {
+				# code...
+				$thiscase[$case] = 1;
+			}
+			foreach ($thiscase as $case => $casevalue) {
+				# code...
+				$strCase .=$case . "; ";
+			}
+			#语法信息替换为本地字符串
+			foreach ($_local->grammastr as $gr) {
+				$strCase = str_replace($gr->id, $gr->value, $strCase);
+			}
+			#拆分查重复
+			$thispart = array();
+			$strPart = "";
+			$arrPart = explode("@",$value["factors"]);
+			foreach ($arrPart as  $part) {
+				# code...
+				$thispart[$part] = 1;
+			}
+			foreach ($thispart as $part => $partvalue) {
+				# code...
+				$strPart .=$part . "; ";
+			}
+
+			#意思查重复
+			$thismean = array();
+			$strMean = "";
+			$arrMean = explode("$",$value["mean"]);
+			foreach ($arrMean as  $mean) {
+				# code...
+				$thismean[$mean] = 1;
+			}
+			foreach ($thismean as $mean => $meanvalue) {
+				# code...
+				$strMean .=$mean . "; ";
+			}
+			$output .= "<div class='mean'><b>语法</b>:{$strCase}</div>";
+			if($key!=="_null_"){
+				$output .= "<div class='mean'><b>原型</b>:<a href='index.php?key={$key}'>{$key}</a></div>";				
+			}
+
+			$output .= "<div class='mean'><b>意思</b>:{$strMean}</div>";
+			$output .= "<div class='mean'><b>组成</b>:{$strPart}</div>";
 		}
 		}
 		$output .= "<div><span>{$_local->gui->contributor}:</span>";
 		$output .= "<div><span>{$_local->gui->contributor}:</span>";
 		$userinfo = new UserInfo();
 		$userinfo = new UserInfo();

+ 3 - 3
app/dict/p_ending.php

@@ -12,8 +12,8 @@ array("ti","tabba",".ti.$.fpp."),
 array("ati","itabba",".ti.$.fpp."),
 array("ati","itabba",".ti.$.fpp."),
 array("eti","itabba",".ti.$.fpp."),
 array("eti","itabba",".ti.$.fpp."),
 array("oti","itabba",".ti.$.fpp."),
 array("oti","itabba",".ti.$.fpp."),
-array("ati","aniya",".ti.$.fpp."),
-array("eti","aniya",".ti.$.fpp."),
-array("oti","aniya",".ti.$.fpp.")
+array("ati","anīya",".ti.$.fpp."),
+array("eti","anīya",".ti.$.fpp."),
+array("oti","anīya",".ti.$.fpp.")
 )
 )
 ?>
 ?>

+ 1 - 0
app/dict/redis_comp_part.php

@@ -1,5 +1,6 @@
 <?php
 <?php
 #从自动复合词数据库中提取数据到ridis
 #从自动复合词数据库中提取数据到ridis
+#已经废弃
 require_once "../path.php";
 require_once "../path.php";
 require_once "../redis/function.php";
 require_once "../redis/function.php";
 
 

+ 9 - 9
app/dict/redis_refresh_first_mean.php

@@ -20,24 +20,24 @@ if (PHP_SAPI == "cli") {
 			print_r($languages);
 			print_r($languages);
 			foreach ($languages as $thisLang) {
 			foreach ($languages as $thisLang) {
 				# code...
 				# code...
+				echo "runing $thisLang \n";
 				$query = "SELECT word,mean from " . _TABLE_DICT_REF_ . " where language = ? group by word";
 				$query = "SELECT word,mean from " . _TABLE_DICT_REF_ . " where language = ? group by word";
-				$meanings = PDO_FetchAll($query, array($thisLang));
-				foreach ($meanings as $mean) {
-					# code...
-					$redis->hSet("ref_first_mean://".$thisLang,$mean["word"],$mean["mean"]);
+				$stmt = $PDO->prepare($query);
+        		$stmt->execute(array($thisLang));
+				while($meaning=$stmt->fetch(PDO::FETCH_ASSOC)){
+					$redis->hSet("ref_first_mean://".$thisLang,$meaning["word"],$meaning["mean"]);
 				}
 				}
 				echo $thisLang.":".$redis->hLen("ref_first_mean://".$thisLang)."\n";
 				echo $thisLang.":".$redis->hLen("ref_first_mean://".$thisLang)."\n";
 			}
 			}
 
 
 			$query = "SELECT word,mean from " . _TABLE_DICT_REF_ . " where 1 group by word";
 			$query = "SELECT word,mean from " . _TABLE_DICT_REF_ . " where 1 group by word";
-			$meanings = PDO_FetchAll($query);
-			foreach ($meanings as $mean) {
-				# code...
-				$redis->hSet("ref_first_mean://com",$mean["word"],$mean["mean"]);
+			$stmt = $PDO->prepare($query);
+			$stmt->execute();
+			while($meaning=$stmt->fetch(PDO::FETCH_ASSOC)){
+				$redis->hSet("ref_first_mean://com",$meaning["word"],$meaning["mean"]);
 			}
 			}
 			echo "com:".$redis->hLen("ref_first_mean://com")."\n";
 			echo "com:".$redis->hLen("ref_first_mean://com")."\n";
 		}
 		}
-
 	}
 	}
 	else{
 	else{
 		echo "no redis server";
 		echo "no redis server";

+ 33 - 0
app/doc/card.php

@@ -0,0 +1,33 @@
+<?php
+//
+
+require_once "../path.php";
+require_once "../public/_pdo.php";
+require_once '../ucenter/function.php';
+
+
+if (isset($_GET["id"])) {
+	$output["id"]=$_GET["id"];
+	PDO_Connect( _FILE_DB_FILEINDEX_);
+	$query = "SELECT title,create_time,user_id as owner FROM fileindex  WHERE id = ? ";
+	$result = PDO_FetchRow($query, array($_GET["id"]));
+	$strData="";
+	if ($result) {
+		
+		$_userinfo = new UserInfo();
+		$name = $_userinfo->getName($result["owner"]);
+
+		$strData .= "<div>标题:".$result["title"]."</div>";
+		$strData .=  "<div>创建人:".$name["nickname"]."</div>";
+		$strData .=  "<div>创建时间:".date("Y/m/d",$result["create_time"]/1000)."</div>";
+	} else {
+		$strData .=  "unkow";
+	}
+	$output["data"] = $strData;
+} else {
+	$output["id"]=0;
+	$output["data"] = "unkow";
+}
+echo json_encode($output, JSON_UNESCAPED_UNICODE);
+
+?>

+ 1 - 1
app/doc/coop.php

@@ -49,7 +49,7 @@ $powerlist["10"] = "仅阅读";
 $powerlist["30"] = "可修改";
 $powerlist["30"] = "可修改";
 //$powerlist["40"] = "管理员";
 //$powerlist["40"] = "管理员";
 
 
-PDO_Connect("" . _FILE_DB_FILEINDEX_);
+PDO_Connect(_FILE_DB_FILEINDEX_);
 
 
 echo "<input id='doc_coop_docid' type='hidden' value='{$_doc_id}' />";
 echo "<input id='doc_coop_docid' type='hidden' value='{$_doc_id}' />";
 $query = "SELECT * from fileindex where id = ? ";
 $query = "SELECT * from fileindex where id = ? ";

+ 20 - 0
app/doc/function.php

@@ -0,0 +1,20 @@
+<?php
+
+require_once "../path.php";
+require_once "../public/_pdo.php";
+
+function pcs_get_title($id)
+{
+    if (isset($id)) {
+		PDO_Connect("" . _FILE_DB_FILEINDEX_);
+		$query = "SELECT title FROM fileindex  WHERE id = ? ";
+		$file = PDO_FetchRow($query, array($id));
+		if ($file) {
+			return $file["title"];
+		} else {
+			return "";
+		}
+    } else {
+        return "";
+    }
+}

+ 3 - 2
app/doc/pcs2db.php

@@ -101,7 +101,7 @@ $query = "SELECT file_name, doc_info, modify_time from fileindex where id=? ";
 $Fetch = PDO_FetchRow($query, array($_GET["doc_id"]));
 $Fetch = PDO_FetchRow($query, array($_GET["doc_id"]));
 
 
 if ($Fetch === false) {
 if ($Fetch === false) {
-    echo "文件不存在";
+    echo "数据库中查不到文件";
     exit;
     exit;
 } else {
 } else {
     $file_modify_time = $Fetch["modify_time"];
     $file_modify_time = $Fetch["modify_time"];
@@ -112,13 +112,14 @@ if ($Fetch === false) {
         exit;
         exit;
     }
     }
 }
 }
+echo "File Name:{$file}<br>";
 if (!file_exists($file)) {
 if (!file_exists($file)) {
     echo "文件不存在";
     echo "文件不存在";
     exit;
     exit;
 }
 }
 $xml = simplexml_load_file($file);
 $xml = simplexml_load_file($file);
 if ($xml == false) {
 if ($xml == false) {
-    echo "载入pcs文件错误";
+    echo "载入pcs文件错误。文件名:{$file}";
     exit;
     exit;
 }
 }
 $xml_head = $xml->xpath('//head')[0];
 $xml_head = $xml->xpath('//head')[0];

+ 27 - 0
app/group/function.php

@@ -31,6 +31,7 @@ class GroupInfo
         $this->dbh = new PDO($dns, "", "", array(PDO::ATTR_PERSISTENT => true));
         $this->dbh = new PDO($dns, "", "", array(PDO::ATTR_PERSISTENT => true));
         $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
         $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
         $buffer = array();
         $buffer = array();
+        $parentId = array();
     }
     }
 
 
     public function getName($id)
     public function getName($id)
@@ -57,5 +58,31 @@ class GroupInfo
             $buffer[$id] = "";
             $buffer[$id] = "";
             return $buffer[$id];
             return $buffer[$id];
         }
         }
+	}
+	
+	public function getParentId($id)
+    {
+        if (empty($id)) {
+            return "";
+        }
+        if (isset($parentId[$id])) {
+            return $parentId[$id];
+        }
+        if ($this->dbh) {
+            $query = "SELECT parent FROM group_info WHERE id= ? ";
+            $stmt = $this->dbh->prepare($query);
+            $stmt->execute(array($id));
+            $user = $stmt->fetch(PDO::FETCH_ASSOC);
+            if ($user) {
+                $parentId[$id] = $user["parent"];
+                return $parentId[$id];
+            } else {
+                $parentId[$id] = "";
+                return $parentId[$id];
+            }
+        } else {
+            $parentId[$id] = "";
+            return $parentId[$id];
+        }
     }
     }
 }
 }

+ 8 - 3
app/group/get.php

@@ -5,6 +5,7 @@ require_once "../path.php";
 require_once "../public/_pdo.php";
 require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
 require_once '../ucenter/function.php';
 require_once '../ucenter/function.php';
+require_once '../share/function.php';
 
 
 $output = array();
 $output = array();
 if (isset($_GET["id"])) {
 if (isset($_GET["id"])) {
@@ -25,8 +26,9 @@ if (isset($_GET["id"])) {
             $parent_group = PDO_FetchRow($query, array($Fetch["parent"]));
             $parent_group = PDO_FetchRow($query, array($Fetch["parent"]));
             $output["parent"] = $parent_group;
             $output["parent"] = $parent_group;
         }
         }
-        #列出组文件
+        #列出组共享资源
         {
         {
+			/*
             PDO_Connect("" . _FILE_DB_FILEINDEX_);
             PDO_Connect("" . _FILE_DB_FILEINDEX_);
             $query = "SELECT * FROM power  WHERE user = ? ";
             $query = "SELECT * FROM power  WHERE user = ? ";
             $fileList = PDO_FetchAll($query, array($id));
             $fileList = PDO_FetchAll($query, array($id));
@@ -39,8 +41,11 @@ if (isset($_GET["id"])) {
                 } else {
                 } else {
                     $fileList[$key]["title"] = "";
                     $fileList[$key]["title"] = "";
                 }
                 }
-            }
-            $output["file"] = $fileList;
+			}
+			
+			$output["file"] = $fileList;
+			*/
+			$output["file"] =share_res_list_get($id);
         }
         }
     }
     }
 }
 }

+ 78 - 22
app/group/group.js

@@ -73,7 +73,7 @@ function my_group_list() {
 							"</a></div>";
 							"</a></div>";
 						html += "<div style='flex:1;'><div class='hover_button'>";
 						html += "<div style='flex:1;'><div class='hover_button'>";
 						if (parseInt(iterator.power) == 0) {
 						if (parseInt(iterator.power) == 0) {
-							//管理员可以删除group
+							//只有管理员可以删除group
 							html +=
 							html +=
 								"<button onclick=\"group_del('" +
 								"<button onclick=\"group_del('" +
 								iterator.group_id +
 								iterator.group_id +
@@ -111,14 +111,17 @@ function group_list(id, list) {
 					let html = "";
 					let html = "";
 					let result = JSON.parse(data);
 					let result = JSON.parse(data);
 					let key = 1;
 					let key = 1;
-					html += "<div class='info_block'>";
-					html += "<h2>" + gLocal.gui.introduction + "</h2>";
-					html += result.info.description;
-					html += "</div>";
+					if (typeof result.info.description != "undefined" && result.info.description.length > 0) {
+						html += "<div class='info_block'>";
+						html += "<h2>" + gLocal.gui.introduction + "</h2>";
+						html += marked(result.info.description);
+						html += "</div>";
+					}
+
 					$("#curr_group").html("/" + result.info.name);
 					$("#curr_group").html("/" + result.info.name);
 
 
 					if (result.parent) {
 					if (result.parent) {
-						//如果是project 显示 group名
+						//如果是project 显示 group名
 						$("#parent_group").html(
 						$("#parent_group").html(
 							" / <a href='../group/index.php?id=" +
 							" / <a href='../group/index.php?id=" +
 								result.parent.id +
 								result.parent.id +
@@ -127,7 +130,9 @@ function group_list(id, list) {
 								"</a> "
 								"</a> "
 						);
 						);
 					} else {
 					} else {
-						if (result.info.creator == getCookie("userid")) {
+						/*
+						关闭子小组功能
+						if (result.info.owner == getCookie("userid")) {
 							$("#button_new_sub_group").show();
 							$("#button_new_sub_group").show();
 						}
 						}
 						//子小组列表
 						//子小组列表
@@ -139,7 +144,7 @@ function group_list(id, list) {
 								html += "<div style='flex:1;'>" + key++ + "</div>";
 								html += "<div style='flex:1;'>" + key++ + "</div>";
 								html += "<div style='flex:2;'>" + iterator.name + "</div>";
 								html += "<div style='flex:2;'>" + iterator.name + "</div>";
 								html += "<div style='flex:2;'>";
 								html += "<div style='flex:2;'>";
-								if (iterator.creator == getCookie("userid")) {
+								if (iterator.owner == getCookie("userid")) {
 									html += gLocal.gui.owner;
 									html += gLocal.gui.owner;
 								}
 								}
 								html += "</div>";
 								html += "</div>";
@@ -150,7 +155,7 @@ function group_list(id, list) {
 									gLocal.gui.enter +
 									gLocal.gui.enter +
 									"</a></div>";
 									"</a></div>";
 								html += "<div style='flex:1;'><div class='hover_button'>";
 								html += "<div style='flex:1;'><div class='hover_button'>";
-								if (iterator.creator == getCookie("userid")) {
+								if (iterator.owner == getCookie("userid")) {
 									html +=
 									html +=
 										"<button onclick=\"group_del('" +
 										"<button onclick=\"group_del('" +
 										iterator.id +
 										iterator.id +
@@ -165,6 +170,7 @@ function group_list(id, list) {
 							html += "尚未设置小组";
 							html += "尚未设置小组";
 						}
 						}
 						html += "</div>";
 						html += "</div>";
+*/
 					}
 					}
 
 
 					//共享文件列表
 					//共享文件列表
@@ -175,25 +181,76 @@ function group_list(id, list) {
 						for (const iterator of result.file) {
 						for (const iterator of result.file) {
 							html += '<div class="file_list_row" style="padding:5px;">';
 							html += '<div class="file_list_row" style="padding:5px;">';
 							html += "<div style='flex:1;'>" + key++ + "</div>";
 							html += "<div style='flex:1;'>" + key++ + "</div>";
-							html += "<div style='flex:2;'>" + iterator.title + "</div>";
+							html += "<div style='flex:1;'>";
+							//资源类型
+							html += "<svg class='icon'>";
+							let cardUrl = "";
+							let doing = "";
+							switch (parseInt(iterator.res_type)) {
+								case 1: //pcs
+									html += "<use xlink:href='../studio/svg/icon.svg#article'></use>";
+									cardUrl = "../doc/card.php";
+									doing +=
+										"<a href='../studio/project.php?op=open&doc_id=" +
+										iterator.res_id +
+										"'>打开</a>";
+									break;
+								case 2: //channel
+									html += "<use xlink:href='../studio/svg/icon.svg#channel_leaves'></use>";
+									cardUrl = "../channal/card.php";
+									break;
+								case 3: //article
+									html += "<use xlink:href='../studio/svg/icon.svg#article_1'></use>";
+									cardUrl = "../article/card.php";
+									doing +=
+										"<a href='../article/?id=" + iterator.res_id + "' target='_blank'>查看</a>";
+									doing +=
+										"|<a href='../article/my_article_edit.php?id=" +
+										iterator.res_id +
+										"' target='_blank'>编辑</a>";
+									break;
+								case 4: //collection
+									html += "<use xlink:href='../studio/svg/icon.svg#collection'></use>";
+									cardUrl = "../collect/card.php";
+									doing +=
+										"<a href='../article/?collect=" +
+										iterator.res_id +
+										"' target='_blank'>查看</a>";
+									doing +=
+										"|<a href='../article/my_collect_edit.php?id=" +
+										iterator.res_id +
+										"' target='_blank'>编辑</a>";
+									break;
+								case 5: //channel片段
+									break;
+								default:
+									html += "unkow";
+									break;
+							}
+
+							html += "</svg>";
+							html += "</div>";
 							html += "<div style='flex:2;'>";
 							html += "<div style='flex:2;'>";
-							switch (iterator.power) {
+							html += "<guide url='" + cardUrl + "' gid='" + iterator.res_id + "'>";
+							html += iterator.res_title + "</guide></div>";
+							html += "<div style='flex:2;'>";
+							switch (parseInt(iterator.power)) {
 								case 10:
 								case 10:
 									html += gLocal.gui.read_only;
 									html += gLocal.gui.read_only;
 									break;
 									break;
 								case 20:
 								case 20:
+									html += gLocal.gui.write;
 									break;
 									break;
 								case 30:
 								case 30:
-									html += gLocal.gui.write;
 									break;
 									break;
 								default:
 								default:
 									break;
 									break;
 							}
 							}
 							html += "</div>";
 							html += "</div>";
-							html +=
-								"<div style='flex:1;'><a href='../studio/project.php?op=open&doc_id=" +
-								iterator.doc_id +
-								"'>打开</a></div>";
+							html += "<div style='flex:1;'>";
+							//可用的操作
+							html += doing;
+							html += "</div>";
 							html += "</div>";
 							html += "</div>";
 						}
 						}
 					} else {
 					} else {
@@ -203,6 +260,7 @@ function group_list(id, list) {
 					html += "</div>";
 					html += "</div>";
 
 
 					$("#my_group_list").html(html);
 					$("#my_group_list").html(html);
+					guide_init();
 				} catch (e) {
 				} catch (e) {
 					console.error(e);
 					console.error(e);
 				}
 				}
@@ -236,17 +294,15 @@ function member_list(id) {
 								html += "拥有者";
 								html += "拥有者";
 							}
 							}
 							html += "</div>";
 							html += "</div>";
-							html += "<div style='flex:1;'><div class='hover_button'>";
-							//if (iterator.creator == getCookie("userid"))
+							html += "<div style='position: absolute;margin-top: -1.5em;right: 1em;'><div class='hover_button'>";
+							//if (iterator.owner == getCookie("userid"))
 							{
 							{
 								html +=
 								html +=
-									"<button onclick=\"member_del('" +
+									"<button style='background: var(--bg-color);' onclick=\"member_del('" +
 									id +
 									id +
 									"','" +
 									"','" +
 									iterator.user_id +
 									iterator.user_id +
-									"')\">" +
-									gLocal.gui.delete +
-									"</button>";
+									"')\">❌</button>";
 							}
 							}
 							html += "</div></div>";
 							html += "</div></div>";
 							html += "</div>";
 							html += "</div>";

+ 2 - 2
app/group/group_del.php

@@ -7,11 +7,11 @@ $respond = array("status" => 0, "message" => "");
 if (isset($_COOKIE["userid"]) && isset($_POST["groupid"])) {
 if (isset($_COOKIE["userid"]) && isset($_POST["groupid"])) {
     PDO_Connect("" . _FILE_DB_GROUP_);
     PDO_Connect("" . _FILE_DB_GROUP_);
     #TODO 先查是否有删除权限
     #TODO 先查是否有删除权限
-    $query = "SELECT parent from group_info where id=? and creator=? ";
+    $query = "SELECT parent from group_info where id=? and owner=? ";
     $gInfo = PDO_FetchRow($query, array($_POST["groupid"], $_COOKIE["userid"]));
     $gInfo = PDO_FetchRow($query, array($_POST["groupid"], $_COOKIE["userid"]));
     if ($gInfo) {
     if ($gInfo) {
         #删除group info
         #删除group info
-        $query = "DELETE from group_info where id=? and creator=? ";
+        $query = "DELETE from group_info where id=? and owner=? ";
         PDO_Execute($query, array($_POST["groupid"], $_COOKIE["userid"]));
         PDO_Execute($query, array($_POST["groupid"], $_COOKIE["userid"]));
         #删除 组员
         #删除 组员
         $query = "DELETE from group_member where group_id=? ";
         $query = "DELETE from group_member where group_id=? ";

+ 2 - 1
app/group/index.php

@@ -90,6 +90,7 @@ require_once '../studio/index_head.php';
 						<div id='group_add_div' class="float_dlg"></div>
 						<div id='group_add_div' class="float_dlg"></div>
 					</span>
 					</span>
 
 
+<!--
 					<span id="button_new_sub_group" class="icon_btn_div">
 					<span id="button_new_sub_group" class="icon_btn_div">
 						<span class="icon_btn_tip"><?php echo $_local->gui->new_sub_group;?></span>
 						<span class="icon_btn_tip"><?php echo $_local->gui->new_sub_group;?></span>
 						<button id="file_add" type="button" class="icon_btn" title=" "  onclick="group_add_dlg_show()">
 						<button id="file_add" type="button" class="icon_btn" title=" "  onclick="group_add_dlg_show()">
@@ -99,7 +100,7 @@ require_once '../studio/index_head.php';
 						</button>
 						</button>
 						<div id='sub_group_add_div' class="float_dlg"></div>
 						<div id='sub_group_add_div' class="float_dlg"></div>
 					</span>
 					</span>
-
+-->
 					<span id="delete" class="icon_btn_div">				
 					<span id="delete" class="icon_btn_div">				
 						<span class="icon_btn_tip"><?php echo $_local->gui->recycle_bin;?></span>
 						<span class="icon_btn_tip"><?php echo $_local->gui->recycle_bin;?></span>
 						<button id="to_recycle" type="button" class="icon_btn" onclick="file_del()" title=" ">
 						<button id="to_recycle" type="button" class="icon_btn" onclick="file_del()" title=" ">

+ 12 - 2
app/group/list.php

@@ -6,9 +6,19 @@ require_once "../public/_pdo.php";
 require_once '../public/function.php';
 require_once '../public/function.php';
 require_once '../ucenter/function.php';
 require_once '../ucenter/function.php';
 
 
-//没有id 列出 我的群组
+//列出 我j参与的群组
 PDO_Connect("" . _FILE_DB_GROUP_);
 PDO_Connect("" . _FILE_DB_GROUP_);
 $query = "SELECT group_name,group_id,power FROM group_member  WHERE level = 0 and user_id=?";
 $query = "SELECT group_name,group_id,power FROM group_member  WHERE level = 0 and user_id=?";
 $Fetch = PDO_FetchAll($query, array($_COOKIE["userid"]));
 $Fetch = PDO_FetchAll($query, array($_COOKIE["userid"]));
-
+foreach ($Fetch as $key => $value) {
+	# code...
+	$query = "SELECT name FROM group_info  WHERE id=?";
+	$groupInfo = PDO_FetchRow($query, array($value["group_id"]));
+	if($groupInfo){
+		$Fetch[$key]["group_name"]=$groupInfo["name"];
+	}
+	else{
+		$Fetch[$key]["group_name"]="";
+	}
+}
 echo json_encode($Fetch, JSON_UNESCAPED_UNICODE);
 echo json_encode($Fetch, JSON_UNESCAPED_UNICODE);

+ 3 - 3
app/group/member_del.php

@@ -18,13 +18,13 @@ if (isset($_POST["groupid"])) {
     $fc = PDO_FetchRow($query, array($_POST["groupid"]));
     $fc = PDO_FetchRow($query, array($_POST["groupid"]));
     if ($fc) {
     if ($fc) {
         if ($fc["parent"] == 0) {
         if ($fc["parent"] == 0) {
-            if ($fc["creator"] == $_COOKIE["userid"]) {
+            if ($fc["owner"] == $_COOKIE["userid"]) {
                 $mypower = 0;
                 $mypower = 0;
             }
             }
         } else {
         } else {
-            $query = "SELECT creator  from group_info where id=?";
+            $query = "SELECT owner  from group_info where id=?";
             $g_parent = PDO_FetchRow($query, array($fc["parent"]));
             $g_parent = PDO_FetchRow($query, array($fc["parent"]));
-            if ($g_parent && $g_parent["creator"] == $_COOKIE["userid"]) {
+            if ($g_parent && $g_parent["owner"] == $_COOKIE["userid"]) {
                 $mypower = 0;
                 $mypower = 0;
             }
             }
         }
         }

+ 13 - 10
app/group/my_group_put.php

@@ -6,8 +6,17 @@ require_once '../public/function.php';
 
 
 $respond = array("status" => 0, "message" => "");
 $respond = array("status" => 0, "message" => "");
 if (isset($_COOKIE["userid"])) {
 if (isset($_COOKIE["userid"])) {
-    PDO_Connect("" . _FILE_DB_GROUP_);
-    $query = "INSERT INTO group_info ( id,  parent  , name  , description ,  status , creator ,create_time )
+    PDO_Connect(_FILE_DB_GROUP_);
+	#先查询是否有重复的组名
+	$query = "SELECT id FROM group_info  WHERE name = ? ";
+    $Fetch = PDO_FetchRow($query, array($_POST["name"]));
+	if ($Fetch) {
+		$respond['status'] = 1;
+        $respond['message'] = "错误:有相同的组名称,请选择另一个名称。";
+		echo json_encode($respond, JSON_UNESCAPED_UNICODE);
+		exit;
+	}
+    $query = "INSERT INTO group_info ( id,  parent  , name  , description ,  status , owner ,create_time )
 	                       VALUES  ( ?, ? , ? , ? , ? , ?  ,? ) ";
 	                       VALUES  ( ?, ? , ? , ? , ? , ?  ,? ) ";
     $sth = $PDO->prepare($query);
     $sth = $PDO->prepare($query);
     $newid = UUID::v4();
     $newid = UUID::v4();
@@ -19,17 +28,11 @@ if (isset($_COOKIE["userid"])) {
         $respond['message'] = $error[2];
         $respond['message'] = $error[2];
     }
     }
 
 
+	#将创建者添加到成员中
     $query = "INSERT INTO group_member (  user_id  , group_id  , power , group_name , level ,  status )
     $query = "INSERT INTO group_member (  user_id  , group_id  , power , group_name , level ,  status )
 		VALUES  (  ? , ? , ? , ? , ?  ,? ) ";
 		VALUES  (  ? , ? , ? , ? , ?  ,? ) ";
     $sth = $PDO->prepare($query);
     $sth = $PDO->prepare($query);
-    if ($_POST["parent"] == 0) {
-        $level = 0;
-        $power = 0;
-    } else {
-        $level = 1;
-        $power = 1;
-    }
-    $sth->execute(array($_COOKIE["userid"], $newid, $power, $_POST["name"], $level, 1));
+    $sth->execute(array($_COOKIE["userid"], $newid, 0, $_POST["name"], 0, 1));
     $respond = array("status" => 0, "message" => "");
     $respond = array("status" => 0, "message" => "");
     if (!$sth || ($sth && $sth->errorCode() != 0)) {
     if (!$sth || ($sth && $sth->errorCode() != 0)) {
         $error = PDO_ErrorInfo();
         $error = PDO_ErrorInfo();

+ 1 - 1
app/guide/guide.css

@@ -10,7 +10,7 @@ guide {
 	background-repeat: no-repeat;
 	background-repeat: no-repeat;
 	background-size: contain;
 	background-size: contain;
 	margin: 0 6px;
 	margin: 0 6px;
-	color: var(--tool-color);
+	color: var(--link-color);
 }
 }
 guide:hover .guide_contence {
 guide:hover .guide_contence {
 	display: inline-block;
 	display: inline-block;

+ 5 - 1
app/guide/guide.js

@@ -20,8 +20,12 @@ function guide_init() {
 			return;
 			return;
 		}
 		}
 		let gid = $(this).attr("gid");
 		let gid = $(this).attr("gid");
+		let url = $(this).attr("url");
+		if (typeof url == "undefined" || url == "") {
+			url = "../guide/get.php";
+		}
 		$.get(
 		$.get(
-			"../guide/get.php",
+			url,
 			{
 			{
 				id: gid,
 				id: gid,
 			},
 			},

+ 1 - 1
app/install/db_insert_sentence.php

@@ -43,7 +43,7 @@ function wordStyle($word, $style)
 
 
         case 'note':
         case 'note':
             # vir note...
             # vir note...
-            return "<note>" . $word . "</note>";
+            return "<n>" . $word . "</n>";
             break;
             break;
         case 'paranum':
         case 'paranum':
             # vir note...
             # vir note...

+ 18 - 8
app/install/step5.php

@@ -150,24 +150,29 @@ $db = $dbfile[5];
 echo '<div style="padding:10px;margin:5px;border-bottom: 1px solid gray;display:flex;">';
 echo '<div style="padding:10px;margin:5px;border-bottom: 1px solid gray;display:flex;">';
 echo '<div style="flex:5;">' . $db[0] . '</div>';
 echo '<div style="flex:5;">' . $db[0] . '</div>';
 echo '<div style="flex:3;">';
 echo '<div style="flex:3;">';
+/*
 if (!file_exists($db[0])) {
 if (!file_exists($db[0])) {
     echo "<span style='color:red;'>数据库不存在</span>";
     echo "<span style='color:red;'>数据库不存在</span>";
     echo "</div>";
     echo "</div>";
     echo '<div style="flex:2;"><a href="step5.php?index=5">建立</a></div>';
     echo '<div style="flex:2;"><a href="step5.php?index=5">建立</a></div>';
-} else {
+} else 
+*/
+{
     echo "<span style='color:green;'>已存在</span>";
     echo "<span style='color:green;'>已存在</span>";
     echo "</div>";
     echo "</div>";
     echo '<div style="flex:2;"><a href="step5.php?index=5">清空</a><span style="color:red;">注意!此操作将删除原数据库中所有数据!</span></div>';
     echo '<div style="flex:2;"><a href="step5.php?index=5">清空</a><span style="color:red;">注意!此操作将删除原数据库中所有数据!</span></div>';
 }
 }
 echo "</div>";
 echo "</div>";
 
 
-if (file_exists(_FILE_DB_PALI_SENTENCE_)) {
+//if (file_exists(_FILE_DB_PALI_SENTENCE_)) 
+{
     echo "Pali句子数据库已经存在<br>";
     echo "Pali句子数据库已经存在<br>";
     echo '<a href="db_insert_sentence.php">重新生成</a>';
     echo '<a href="db_insert_sentence.php">重新生成</a>';
-} else {
-    echo "Pali句子数据库不存在<br>";
-    echo '<a href="db_insert_sentence.php">生成</a>';
-}
+} 
+//else {
+//    echo "Pali句子数据库不存在<br>";
+//    echo '<a href="db_insert_sentence.php">生成</a>';
+//}
 ?>
 ?>
 </div>
 </div>
 
 
@@ -222,13 +227,18 @@ if (!file_exists($db[0])) {
 }
 }
 echo "</div>";
 echo "</div>";
 
 
-if (file_exists(_FILE_DB_PALITEXT_)) {
+//if (file_exists(_FILE_DB_PALITEXT_)) 
+{
     echo "标题索引数据库已经存在<br>";
     echo "标题索引数据库已经存在<br>";
     echo '<a href="db_update_toc.php" target="_blank">更新</a><br>';
     echo '<a href="db_update_toc.php" target="_blank">更新</a><br>';
-} else {
+} 
+/*
+else 
+{
     echo "标题索引数据库不存在<br>";
     echo "标题索引数据库不存在<br>";
     echo '<div style="flex:2;"><a href="step5.php?index=' . $i . '">建立</a></div>';
     echo '<div style="flex:2;"><a href="step5.php?index=' . $i . '">建立</a></div>';
 }
 }
+*/
 echo "<a href = '" . _DIR_LOG_ . "/db_update_title.log" . "' target='_blank'>view Log</a>"
 echo "<a href = '" . _DIR_LOG_ . "/db_update_title.log" . "' target='_blank'>view Log</a>"
 ?>
 ?>
 </div>
 </div>

+ 1 - 1
app/lang/lang.php

@@ -104,7 +104,7 @@
 
 
 	.disable {
 	.disable {
 		color: var(--new-tool-content-disabled);
 		color: var(--new-tool-content-disabled);
-		cursor: default;
+		cursor: not-allowed;
 	}
 	}
 
 
 	/*
 	/*

+ 12 - 10
app/redis/upgrade_pali_sent.php → app/pali_sent/redis_upgrade_pali_sent.php

@@ -1,10 +1,13 @@
 <?php
 <?php
 /*
 /*
-get user sentence from db
+从巴利句子列表数据库中提取数据填充redis
+每个句子包含 
+pali 
+id 
+sim_count
  */
  */
 require_once "../path.php";
 require_once "../path.php";
-require_once "../public/_pdo.php";
-require_once "../public/function.php";
+require_once "../redis/function.php";
 
 
 if (isset($argv[1])) {
 if (isset($argv[1])) {
     if ($argv[1] == "del") {
     if ($argv[1] == "del") {
@@ -21,21 +24,20 @@ if (isset($argv[1])) {
 		echo "delete ok ".$count;
 		echo "delete ok ".$count;
     }
     }
 } else {
 } else {
-    $dns = "" . _FILE_DB_PALI_SENTENCE_;
-    $dbh = new PDO($dns, "", "", array(PDO::ATTR_PERSISTENT => true));
+
+    $dbh = new PDO(_FILE_DB_PALI_SENTENCE_, "", "", array(PDO::ATTR_PERSISTENT => true));
     $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
     $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
-	$dns = "" . _FILE_DB_PALI_SENTENCE_SIM_;
-	$db_pali_sent_sim = new PDO($dns, "", "", array(PDO::ATTR_PERSISTENT => true));
+
+	$db_pali_sent_sim = new PDO(_FILE_DB_PALI_SENTENCE_SIM_, "", "", array(PDO::ATTR_PERSISTENT => true));
 	$db_pali_sent_sim->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 	$db_pali_sent_sim->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
 
 
     $query = "SELECT id, book,paragraph, begin,end ,html FROM pali_sent WHERE 1 ";
     $query = "SELECT id, book,paragraph, begin,end ,html FROM pali_sent WHERE 1 ";
     $stmt = $dbh->prepare($query);
     $stmt = $dbh->prepare($query);
     $stmt->execute();
     $stmt->execute();
-    $redis = new redis();
-    $r_conn = $redis->connect('127.0.0.1', 6379);
+    $redis = redis_connect();
 	$stringSize = 0;
 	$stringSize = 0;
 	$count = 0;
 	$count = 0;
-    if ($r_conn) {
+    if ($redis) {
         while ($sent = $stmt->fetch(PDO::FETCH_ASSOC)) {
         while ($sent = $stmt->fetch(PDO::FETCH_ASSOC)) {
 			$count++;
 			$count++;
             $stringSize += strlen($sent["html"]);
             $stringSize += strlen($sent["html"]);

+ 93 - 0
app/pali_text/function.php

@@ -0,0 +1,93 @@
+<?php
+require_once "../path.php";
+require_once "../db/table.php";
+
+class PaliText extends Table
+{
+    function __construct($redis=false) {
+		parent::__construct(_FILE_DB_PALITEXT_, "pali_text", "", "",$redis);
+    }
+	
+	public function getPath($book,$para){
+		if($this->redis!==false){
+			$path = $this->redis->hGet("pali_text://path",$book."-".$para);
+			if($path!==FALSE){
+				return json_decode($path,true);
+			}
+		}
+		$path = array();
+		$parent = $para;
+		$deep = 0;
+		$sFirstParentTitle = "";
+		//循环查找父标题 得到整条路径
+		while ($parent > -1) {
+			$query = "select * from pali_text where \"book\" = ? and \"paragraph\" = ? limit 0,1";
+			$stmt = $this->dbh->prepare($query);
+			$stmt->execute(array($book, $parent));
+			$FetParent = $stmt->fetch(PDO::FETCH_ASSOC);
+
+			$toc =array("book"=>$book,"para"=>$parent,"level"=>$FetParent["level"],"title"=>$FetParent["toc"]);
+
+			$path[] = $toc;
+
+			$parent = $FetParent["parent"];
+			$deep++;
+			if ($deep > 5) {
+				break;
+			}
+		}
+		if($this->redis){
+			$this->redis->hSet("pali_text://path",$book."-".$para,json_encode($path,JSON_UNESCAPED_UNICODE));
+		}
+		return ($path);
+	}
+
+	public function getPathHtml($arrPath){
+		$path="";
+		foreach ($arrPath as $key => $value) {
+			# code...
+			$toc = "<chapter book='{$value["book"]}' para='{$value["para"]}' title='{$value["title"]}'>{$value["title"]}</chapter>";
+			if ($path == "") {
+				if ($value["level"] < 100) {
+					$path = $toc;
+				} else {
+					$path = "<para book='{$value["book"]}' para='{$value["para"]}' title='{$value["title"]}'>{$value["para"]}</para>";
+				}
+			} else {
+				$path = $toc . $path;
+			}
+		}
+		return $path;
+	}
+}
+
+class PaliBook extends Table
+{
+    function __construct($redis=false) {
+		parent::__construct(_FILE_DB_PALITEXT_, "books", "", "",$redis);
+    }
+	
+	public function getBookTitle($book,$para){
+		if($this->redis!==false){
+			$result = $this->redis->hGet("pali_text://book",$book."-".$para);
+			if($result!==FALSE){
+				return $result;
+			}
+		}
+		$query = "select title from books where \"book\" = ? and \"paragraph\" = ? limit 0,1";
+		$stmt = $this->dbh->prepare($query);
+		$stmt->execute(array($book, $para));
+		$book = $stmt->fetch(PDO::FETCH_ASSOC);
+		if($book){
+			if($this->redis){
+				$this->redis->hSet("pali_text://book",$book."-".$para,$book["title"]);
+			}			
+			return $book["title"];
+		}
+		else{
+			return false;
+		}
+	}
+}
+
+?>

+ 10 - 6
app/palicanon/palicanon.js

@@ -101,7 +101,7 @@ function tag_changed() {
 					}
 					}
 				}
 				}
 
 
-				if (arrBookList.length < 100 || (arrBookList.length > 100 && iterator.level == 1)) {
+				if (arrBookList.length < 50 || (arrBookList.length > 50 && iterator.level == 1)) {
 					arrChapter.push(iterator);
 					arrChapter.push(iterator);
 				}
 				}
 			}
 			}
@@ -117,6 +117,11 @@ function tag_changed() {
 			tag_render_others();
 			tag_render_others();
 			palicanon_chapter_list_apply(0);
 			palicanon_chapter_list_apply(0);
 			$("#list-1").html(render_chapter_list(arrChapter));
 			$("#list-1").html(render_chapter_list(arrChapter));
+			if (arrBookList.length < 50) {
+				$("#list_shell_1").removeClass("book_view");
+			} else {
+				$("#list_shell_1").addClass("book_view");
+			}
 		}
 		}
 	);
 	);
 }
 }
@@ -407,11 +412,10 @@ function sortNumber(a, b) {
 }
 }
 
 
 function tag_list_slide_toggle(element) {
 function tag_list_slide_toggle(element) {
-	if($(element).html().indexOf("⮟")!= -1){
-		$(element).html("⮝")
-	}
-	else{
-		$(element).html("⮟")
+	if ($(element).html().indexOf("⮟") != -1) {
+		$(element).html("⮝");
+	} else {
+		$(element).html("⮟");
 	}
 	}
 	$("#tag_list").slideToggle();
 	$("#tag_list").slideToggle();
 }
 }

+ 20 - 5
app/palicanon/style.css

@@ -163,6 +163,7 @@ tag {
 }
 }
 .chapter_list ul li:hover {
 .chapter_list ul li:hover {
 	background-color: darkorange;
 	background-color: darkorange;
+	cursor: pointer;
 }
 }
 .chapter_list .resource {
 .chapter_list .resource {
 	display: flex;
 	display: flex;
@@ -170,6 +171,7 @@ tag {
 	margin-bottom: auto;
 	margin-bottom: auto;
 }
 }
 .chapter_list .title .title_1 {
 .chapter_list .title .title_1 {
+	color: var(--main-color);
 	font-weight: 700;
 	font-weight: 700;
 	font-size: 110%;
 	font-size: 110%;
 }
 }
@@ -215,11 +217,7 @@ tag {
 	text-align: center;
 	text-align: center;
 }
 }
 .chapter_head .title .title_1 {
 .chapter_head .title .title_1 {
-	font-size: 120%;
-	padding: 1em 0 0.5em 0;
-	font-weight: 700;
-}
-.chapter_head .title .title_1 {
+	color: var(--main-color);
 	font-size: 120%;
 	font-size: 120%;
 	padding: 1em 0 0.5em 0;
 	padding: 1em 0 0.5em 0;
 	font-weight: 700;
 	font-weight: 700;
@@ -256,3 +254,20 @@ tag {
 	display: flex;
 	display: flex;
 	justify-content: space-between;
 	justify-content: space-between;
 }
 }
+
+.book_view .grid {
+	width: 100%;
+}
+.book_view ul {
+	border-right: unset;
+}
+.book_view ul li {
+	width: 180px;
+	height: 250px;
+	margin: 1em;
+	border: 10px solid wheat;
+}
+.book_view ul {
+	display: flex;
+	flex-flow: wrap;
+}

+ 1 - 0
app/path.php

@@ -120,3 +120,4 @@ define("_FILE_DB_HOSTSETTING_", "sqlite:" . __DIR__ . "/../tmp/user/hostsetting.
 define("_FILE_DB_USER_SENTENCE_HISTORAY_", "sqlite:" . __DIR__ . "/../tmp/user/usent_historay.db3");
 define("_FILE_DB_USER_SENTENCE_HISTORAY_", "sqlite:" . __DIR__ . "/../tmp/user/usent_historay.db3");
 define("_FILE_DB_USER_ACTIVE_", "sqlite:" . __DIR__ . "/../tmp/user/user_active.db3");
 define("_FILE_DB_USER_ACTIVE_", "sqlite:" . __DIR__ . "/../tmp/user/user_active.db3");
 define("_FILE_DB_USER_ACTIVE_LOG_", "sqlite:" . __DIR__ . "/../tmp/user/user_active_log.db3");
 define("_FILE_DB_USER_ACTIVE_LOG_", "sqlite:" . __DIR__ . "/../tmp/user/user_active_log.db3");
+define("_FILE_DB_USER_SHARE_", "sqlite:" . __DIR__ . "/../tmp/user/share.db3");

+ 67 - 66
app/pcdl/css/basic_style.css

@@ -1,23 +1,23 @@
 body {
 body {
-    font-family: "Noto Sans", "Noto Sans SC", "Noto Sans TC", Arial, Verdana;
-    font-style: normal;
-    color: var(--main-color);
-    font-weight: 400;
-    font-size: 13px;
-    overflow-x: hidden;
-    margin: 0;
+	font-family: "Noto Sans", "Noto Sans SC", "Noto Sans TC", Arial, Verdana;
+	font-style: normal;
+	color: var(--main-color);
+	font-weight: 400;
+	font-size: 13px;
+	overflow-x: hidden;
+	margin: 0;
 }
 }
 
 
 div {
 div {
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	box-sizing: border-box;
 }
 }
 
 
 hr {
 hr {
-    margin: 14px 0;
-    border: 1px solid var(--tool-line-color);
-    transform: scaleY(0.4);
+	margin: 14px 0;
+	border: 1px solid var(--tool-line-color);
+	transform: scaleY(0.4);
 }
 }
 
 
 input,
 input,
@@ -30,98 +30,99 @@ hr,
 h1,
 h1,
 h2,
 h2,
 h3 {
 h3 {
-    font-family: inherit;
-    font-size: inherit;
-    font-style: inherit;
-    font-weight: inherit;
-    color: inherit;
-    background-color: inherit;
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
+	font-family: inherit;
+	font-size: inherit;
+	font-style: inherit;
+	font-weight: inherit;
+	color: inherit;
+	background-color: inherit;
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	box-sizing: border-box;
 }
 }
 
 
 input[type="text"],
 input[type="text"],
 input[type="input"],
 input[type="input"],
 textarea {
 textarea {
-    font-weight: 300;
-    width: 100%;
-    border: 1px solid var(--btn-border-line-color);
-    border-radius: 4px;
-    padding: 0.1em 0.3em;
-    font-size: 100%;
-    min-height: 1.2em;
-    margin: 2px 0;
+	font-weight: 300;
+	width: 100%;
+	border: 1px solid var(--btn-border-line-color);
+	border-radius: 4px;
+	padding: 0.1em 0.3em;
+	font-size: 100%;
+	min-height: 1.2em;
+	margin: 2px 0;
 }
 }
 
 
 select {
 select {
-    font-weight: 400;
-    border: 1px solid #aaaaaa;
-    padding: 2px 4px;
-    min-height: 2em;
-    border-radius: 4px;
-    cursor: pointer;
-    margin: 3px 0;
+	font-weight: 400;
+	border: 1px solid #aaaaaa;
+	padding: 2px 4px;
+	min-height: 2em;
+	border-radius: 4px;
+	cursor: pointer;
+	margin: 3px 0;
 }
 }
 
 
 ul,
 ul,
 li {
 li {
-    white-space: normal;
-    color: inherit;
-    margin: 0px;
-    padding: 0px;
-    word-break: keep-all;
-    text-overflow: ellipsis;
-    list-style-type: none;
+	white-space: normal;
+	color: inherit;
+	margin: 0px;
+	padding: 0px;
+	word-break: keep-all;
+	text-overflow: ellipsis;
+	list-style-type: none;
 }
 }
 
 
 a,
 a,
 a:link,
 a:link,
 a:visited {
 a:visited {
-    color: var(--link-color);
-    text-decoration: none;
-    cursor: pointer;
+	color: var(--link-color);
+	text-decoration: none;
+	cursor: pointer;
+	white-space: nowrap;
 }
 }
 
 
 a:focus {
 a:focus {
-    outline: 1px dotted;
+	outline: 1px dotted;
 }
 }
 
 
 a:hover,
 a:hover,
 a:active {
 a:active {
-    color: var(--tool-link-hover-color);
-    outline: none;
+	color: var(--tool-link-hover-color);
+	outline: none;
 }
 }
 
 
 .footer_navbar {
 .footer_navbar {
-    display: flex;
-    flex-wrap: wrap;
-    width: 100%;
+	display: flex;
+	flex-wrap: wrap;
+	width: 100%;
 }
 }
 
 
 .icon {
 .icon {
-    height: 20px;
-    width: 20px;
-    fill: #555;
-    transition: all 0.2s ease;
+	height: 20px;
+	width: 20px;
+	fill: #555;
+	transition: all 0.2s ease;
 }
 }
 
 
 .small_icon {
 .small_icon {
-    height: 1em;
-    width: 1em;
-    fill: #555;
-    -webkit-transition-duration: 0.2s;
-    transition-duration: 0.2s;
+	height: 1em;
+	width: 1em;
+	fill: #555;
+	-webkit-transition-duration: 0.2s;
+	transition-duration: 0.2s;
 }
 }
 
 
 .broder-1 {
 .broder-1 {
-    border: 1px solid var(--border-line-color);
+	border: 1px solid var(--border-line-color);
 }
 }
 
 
 .broder-2 {
 .broder-2 {
-    border: 2px solid var(--border-line-color);
+	border: 2px solid var(--border-line-color);
 }
 }
 
 
 .broder-r {
 .broder-r {
-    border-radius: 5px;
-}
+	border-radius: 5px;
+}

+ 44 - 41
app/pcdl/css/color_day.css

@@ -1,42 +1,45 @@
 :root {
 :root {
-    --bg-color: #FFFFFF;
-    --main-color: #1E1E1E;
-    --main-color1: #626262;
-    --main-color1: #626262;
-    --btn-color: #DFDFDF;
-    --btn-bg-color: #DFDFDF;
-    --btn-border-color: #7D7D7D;
-    --btn-hover-color: #FFFFFF;
-    --btn-hover-bg-color: #494949;
-    --btn-border-line-color: #7D7D7D --input-bg-color: #424242;
-    --select-bg-color: #424242;
-    --drop-bg-color: #EBEBEB;
-    --link-color: #6baaff;
-    --link-hover-color: #1F7DF5;
-    --border-line-color: #C5C7CB;
-    --border: 1px solid var(--border-line-color);
-    --border-shadow: #D2D2D2;
-    --shadow-color: rgba(0, 0, 0, 0.28);
-    --mean-user-color: #F9468F;
-    --tool-bg-color: #333333;
-    --tool-color: #FFFFFF;
-    --tool-bt-bg-color: #222222;
-    --tool-bt-color: #FFFFFF;
-    --tool-bt-bg-hover-color: #545454;
-    --tool-bt-hover-color: #FFFFFF;
-    --tool-bt-border-line-color: #C5C7CB;
-    --tool-bg-color1: #ebebeb;
-    --tool-color1: rgb(49, 49, 49);
-    --tool-bt-bg-color1: #222222;
-    --tool-bt-color1: #FFFFFF;
-    --tool-bt-bg-hover-color1: #545454;
-    --tool-bt-hover-color1: #FFFFFF;
-    --tool-bt-border-line-color1: #C5C7CB;
-    --tool-title-color: #1B1C1C;
-    --tool-line-color: #A4A4A4;
-    --tool-link-hover-color: #A1C9FF;
-    --nocolor: rgba(255, 255, 255, 0);
-    --box-bg-color1: #545454;
-    --box-bg-color2: #545454;
-    --info-bg-color: rgba(255, 255, 255, 0.8);
-}
+	--bg-color: #ffffff;
+	--main-color: #1e1e1e;
+	--main-color1: #626262;
+	--main-color1: #626262;
+	--btn-color: #dfdfdf;
+	--btn-bg-color: #dfdfdf;
+	--btn-border-color: #7d7d7d;
+	--btn-hover-color: #ffffff;
+	--btn-hover-bg-color: #494949;
+	--btn-border-line-color: #7d7d7d;
+	--input-bg-color: #424242;
+	--select-bg-color: #424242;
+	--drop-bg-color: #ebebeb;
+	--link-color: #6baaff;
+	--link-hover-color: #1f7df5;
+	--border-line-color: #c5c7cb;
+	--border: 1px solid var(--border-line-color);
+	--border-shadow: #d2d2d2;
+	--shadow-color: rgba(0, 0, 0, 0.28);
+	--mean-user-color: #f9468f;
+	--tool-bg-color: #333333;
+	--tool-color: #ffffff;
+	--tool-bt-bg-color: #222222;
+	--tool-bt-color: #ffffff;
+	--tool-bt-bg-hover-color: #545454;
+	--tool-bt-hover-color: #ffffff;
+	--tool-bt-border-line-color: #c5c7cb;
+	--tool-bg-color1: #ebebeb;
+	--tool-color1: rgb(49, 49, 49);
+	--tool-bt-bg-color1: #222222;
+	--tool-bt-color1: #ffffff;
+	--tool-bt-bg-hover-color1: #545454;
+	--tool-bt-hover-color1: #ffffff;
+	--tool-bt-border-line-color1: #c5c7cb;
+	--tool-title-color: #1b1c1c;
+	--tool-line-color: #a4a4a4;
+	--tool-link-hover-color: #a1c9ff;
+	--nocolor: rgba(255, 255, 255, 0);
+	--box-bg-color1: #545454;
+	--box-bg-color2: #545454;
+	--info-bg-color: rgba(255, 255, 255, 0.8);
+	--booka: #ddddff;
+	--bookx: #e4e4e4;
+}

+ 220 - 247
app/pcdl/css/font.css

@@ -4,381 +4,354 @@
 /*Pāli Roma*/
 /*Pāli Roma*/
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: normal;
-  font-weight: 100;
-  src: local("Noto Sans Thin"),
-    url(../../../font/NotoSans/NotoSans-Thin.ttf) format("truetype");
-  font-display: fallback;
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: normal;
+	font-weight: 100;
+	src: local("Noto Sans Thin"), url(../../../font/NotoSans/NotoSans-Thin.ttf) format("truetype");
+	font-display: fallback;
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: normal;
-  font-weight: 300;
-  src: local("Noto Sans Light"),
-    url(../../../font/NotoSans/NotoSans-Light.ttf) format("truetype");
-  font-display: fallback;
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: normal;
+	font-weight: 300;
+	src: local("Noto Sans Light"), url(../../../font/NotoSans/NotoSans-Light.ttf) format("truetype");
+	font-display: fallback;
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: normal;
-  font-weight: 400;
-  src: local("Noto Sans Regular"),
-    url(../../../font/NotoSans/NotoSans-Regular.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: normal;
+	font-weight: 400;
+	src: local("Noto Sans Regular"), url(../../../font/NotoSans/NotoSans-Regular.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: normal;
-  font-weight: 500;
-  src: local("Noto Sans Medium"),
-    url(../../../font/NotoSans/NotoSans-Medium.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: normal;
+	font-weight: 500;
+	src: local("Noto Sans Medium"), url(../../../font/NotoSans/NotoSans-Medium.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: normal;
-  font-weight: 700;
-  src: local("Noto Sans Bold"),
-    url(../../../font/NotoSans/NotoSans-Bold.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: normal;
+	font-weight: 700;
+	src: local("Noto Sans Bold"), url(../../../font/NotoSans/NotoSans-Bold.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: normal;
-  font-weight: 900;
-  src: local("Noto Sans Black"),
-    url(../../../font/NotoSans/NotoSans-Black.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: normal;
+	font-weight: 900;
+	src: local("Noto Sans Black"), url(../../../font/NotoSans/NotoSans-Black.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: italic;
-  font-weight: 100;
-  src: local("Noto Sans Thin Italic"),
-    url(../../../font/NotoSans/NotoSans-ThinItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: italic;
+	font-weight: 100;
+	src: local("Noto Sans Thin Italic"), url(../../../font/NotoSans/NotoSans-ThinItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: italic;
-  font-weight: 300;
-  src: local("Noto Sans Light Italic"),
-    url(../../../font/NotoSans/NotoSans-LightItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: italic;
+	font-weight: 300;
+	src: local("Noto Sans Light Italic"), url(../../../font/NotoSans/NotoSans-LightItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: italic;
-  font-weight: 400;
-  src: local("Noto Sans Italic"),
-    url(../../../font/NotoSans/NotoSans-Italic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: italic;
+	font-weight: 400;
+	src: local("Noto Sans Italic"), url(../../../font/NotoSans/NotoSans-Italic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: italic;
-  font-weight: 500;
-  src: local("Noto Sans Medium Italic"),
-    url(../../../font/NotoSans/NotoSans-MediumItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: italic;
+	font-weight: 500;
+	src: local("Noto Sans Medium Italic"), url(../../../font/NotoSans/NotoSans-MediumItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: italic;
-  font-weight: 700;
-  src: local("Noto Sans Bold Italic"),
-    url(../../../font/NotoSans/NotoSans-BoldItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: italic;
+	font-weight: 700;
+	src: local("Noto Sans Bold Italic"), url(../../../font/NotoSans/NotoSans-BoldItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans";
-  font-style: italic;
-  font-weight: 900;
-  src: local("Noto Sans Black Italic"),
-    url(../../../font/NotoSans/NotoSans-BlackItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Sans";
+	font-style: italic;
+	font-weight: 900;
+	src: local("Noto Sans Black Italic"), url(../../../font/NotoSans/NotoSans-BlackItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: normal;
-  font-weight: 100;
-  src: local("Noto Serif Thin"),
-    url(../../../font/NotoSerif/NotoSerif-Thin.ttf) format("truetype");
-  font-display: fallback;
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: normal;
+	font-weight: 100;
+	src: local("Noto Serif Thin"), url(../../../font/NotoSerif/NotoSerif-Thin.ttf) format("truetype");
+	font-display: fallback;
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: normal;
-  font-weight: 300;
-  src: local("Noto Serif Light"),
-    url(../../../font/NotoSerif/NotoSerif-Light.ttf) format("truetype");
-  font-display: fallback;
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: normal;
+	font-weight: 300;
+	src: local("Noto Serif Light"), url(../../../font/NotoSerif/NotoSerif-Light.ttf) format("truetype");
+	font-display: fallback;
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: normal;
-  font-weight: 400;
-  src: local("Noto Serif Regular"),
-    url(../../../font/NotoSerif/NotoSerif-Regular.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: normal;
+	font-weight: 400;
+	src: local("Noto Serif Regular"), url(../../../font/NotoSerif/NotoSerif-Regular.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: normal;
-  font-weight: 500;
-  src: local("Noto Serif Medium"),
-    url(../../../font/NotoSerif/NotoSerif-Medium.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: normal;
+	font-weight: 500;
+	src: local("Noto Serif Medium"), url(../../../font/NotoSerif/NotoSerif-Medium.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: normal;
-  font-weight: 600;
-  src: local("Noto Serif SemiBold"),
-    url(../../../font/NotoSerif/NotoSerif-SemiBold.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: normal;
+	font-weight: 600;
+	src: local("Noto Serif SemiBold"), url(../../../font/NotoSerif/NotoSerif-SemiBold.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: normal;
-  font-weight: 700;
-  src: local("Noto Serif Bold"),
-    url(../../../font/NotoSerif/NotoSerif-Bold.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: normal;
+	font-weight: 700;
+	src: local("Noto Serif Bold"), url(../../../font/NotoSerif/NotoSerif-Bold.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: normal;
-  font-weight: 900;
-  src: local("Noto Serif Black"),
-    url(../../../font/NotoSerif/NotoSerif-Black.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: normal;
+	font-weight: 900;
+	src: local("Noto Serif Black"), url(../../../font/NotoSerif/NotoSerif-Black.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: italic;
-  font-weight: 100;
-  src: local("Noto Serif Thin Italic"),
-    url(../../../font/NotoSerif/NotoSerif-ThinItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: italic;
+	font-weight: 100;
+	src: local("Noto Serif Thin Italic"), url(../../../font/NotoSerif/NotoSerif-ThinItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: italic;
-  font-weight: 300;
-  src: local("Noto Serif Light Italic"),
-    url(../../../font/NotoSerif/NotoSerif-LightItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: italic;
+	font-weight: 300;
+	src: local("Noto Serif Light Italic"), url(../../../font/NotoSerif/NotoSerif-LightItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: italic;
-  font-weight: 400;
-  src: local("Noto Serif Italic"),
-    url(../../../font/NotoSerif/NotoSerif-Italic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: italic;
+	font-weight: 400;
+	src: local("Noto Serif Italic"), url(../../../font/NotoSerif/NotoSerif-Italic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: italic;
-  font-weight: 500;
-  src: local("Noto Serif Medium Italic"),
-    url(../../../font/NotoSerif/NotoSerif-MediumItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: italic;
+	font-weight: 500;
+	src: local("Noto Serif Medium Italic"), url(../../../font/NotoSerif/NotoSerif-MediumItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: italic;
-  font-weight: 600;
-  src: local("Noto Serif SemiBold Italic"),
-    url(../../../font/NotoSerif/NotoSerif-SemiBoldItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: italic;
+	font-weight: 600;
+	src: local("Noto Serif SemiBold Italic"),
+		url(../../../font/NotoSerif/NotoSerif-SemiBoldItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: italic;
-  font-weight: 700;
-  src: local("Noto Serif Bold Italic"),
-    url(../../../font/NotoSerif/NotoSerif-BoldItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: italic;
+	font-weight: 700;
+	src: local("Noto Serif Bold Italic"), url(../../../font/NotoSerif/NotoSerif-BoldItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Serif";
-  font-style: italic;
-  font-weight: 900;
-  src: local("Noto Serif Black Italic"),
-    url(../../../font/NotoSerif/NotoSerif-BlackItalic.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Noto Serif";
+	font-style: italic;
+	font-weight: 900;
+	src: local("Noto Serif Black Italic"), url(../../../font/NotoSerif/NotoSerif-BlackItalic.ttf) format("truetype");
+	font-display: fallback;
 }
 }
 
 
 /*缅文*/
 /*缅文*/
 @font-face {
 @font-face {
-  font-family: "Padauk";
-  font-style: normal;
-  font-weight: 400;
-  src: local("Padauk"),
-    url(../../../font/Padauk/Padauk-Regular.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Padauk";
+	font-style: normal;
+	font-weight: 400;
+	src: local("Padauk"), url(../../../font/Padauk/Padauk-Regular.ttf) format("truetype");
+	font-display: fallback;
 }
 }
-
 @font-face {
 @font-face {
-  font-family: "Padauk";
-  font-style: normal;
-  font-weight: 700;
-  src: local("Padauk Bold"),
-    url(../../../font/Padauk/Padauk-Bold.ttf) format("truetype");
-  font-display: fallback;
+	font-family: "Padauk";
+	font-style: normal;
+	font-weight: 700;
+	src: local("Padauk Bold"), url(../../../font/Padauk/Padauk-Bold.ttf) format("truetype");
+	font-display: fallback;
+}
+/*傣仂文*/
+@font-face {
+	font-family: "ATaiThamKHNewV3-Normal";
+	font-style: normal;
+	font-weight: 400;
+	src: local("A Tai Tham KH New V3"), url(../../../font/taitham/tai-tham-kh-new-v3.ttf) format("truetype");
+	font-display: fallback;
 }
 }
+
 /*中文繁體*/
 /*中文繁體*/
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans TC";
-  font-style: normal;
-  font-weight: 300;
-  src: local("Noto Sans TC Light"), local("Noto Sans CJK TC Light"),
-    local("Source Han Sans TWHK Light");
-  /*url(../../font/NotoSansTC/NotoSansCJKtc-Light.otf) format('opentype'),*/
-  /*url(../../font/NotoSansTC/NotoSansTC-Light.woff) format('woff')*/
+	font-family: "Noto Sans TC";
+	font-style: normal;
+	font-weight: 300;
+	src: local("Noto Sans TC Light"), local("Noto Sans CJK TC Light"), local("Source Han Sans TWHK Light");
+	/*url(../../font/NotoSansTC/NotoSansCJKtc-Light.otf) format('opentype'),*/
+	/*url(../../font/NotoSansTC/NotoSansTC-Light.woff) format('woff')*/
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans TC";
-  font-style: normal;
-  font-weight: 400;
-  src: local("Noto Sans TC Regular"), local("Noto Sans CJK TC Regular"),
-    local("Source Han Sans TWHK Regular");
-  /*url(../../font/NotoSansTC/NotoSansCJKtc-Regular.otf) format('opentype'),*/
-  /*url(../../font/NotoSansTC/NotoSansTC-Regular.woff) format('woff');*/
-  font-display: fallback;
+	font-family: "Noto Sans TC";
+	font-style: normal;
+	font-weight: 400;
+	src: local("Noto Sans TC Regular"), local("Noto Sans CJK TC Regular"), local("Source Han Sans TWHK Regular");
+	/*url(../../font/NotoSansTC/NotoSansCJKtc-Regular.otf) format('opentype'),*/
+	/*url(../../font/NotoSansTC/NotoSansTC-Regular.woff) format('woff');*/
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans TC";
-  font-style: normal;
-  font-weight: 500;
-  src: local("Noto Sans TC Medium"), local("Noto Sans CJK TC Medium"),
-    local("Source Han Sans TWHK Medium");
-  /*url(../../font/NotoSansTC/NotoSansCJKtc-Medium.otf) format('opentype'),*/
-  /*url(../../font/NotoSansTC/NotoSansTC-Medium.woff) format('woff');*/
-  font-display: fallback;
+	font-family: "Noto Sans TC";
+	font-style: normal;
+	font-weight: 500;
+	src: local("Noto Sans TC Medium"), local("Noto Sans CJK TC Medium"), local("Source Han Sans TWHK Medium");
+	/*url(../../font/NotoSansTC/NotoSansCJKtc-Medium.otf) format('opentype'),*/
+	/*url(../../font/NotoSansTC/NotoSansTC-Medium.woff) format('woff');*/
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans TC";
-  font-style: normal;
-  font-weight: 700;
-  src: local("Noto Sans TC Bold"), local("Noto Sans CJK TC Bold"),
-    local("Source Han Sans TWHK Bold");
-  /*url(../../font/NotoSansTC/NotoSansCJKtc-Bold.otf) format('opentype'),*/
-  /*url(../../font/NotoSansTC/NotoSansTC-Bold.woff) format('woff');*/
-  font-display: fallback;
+	font-family: "Noto Sans TC";
+	font-style: normal;
+	font-weight: 700;
+	src: local("Noto Sans TC Bold"), local("Noto Sans CJK TC Bold"), local("Source Han Sans TWHK Bold");
+	/*url(../../font/NotoSansTC/NotoSansCJKtc-Bold.otf) format('opentype'),*/
+	/*url(../../font/NotoSansTC/NotoSansTC-Bold.woff) format('woff');*/
+	font-display: fallback;
 }
 }
 
 
 /*中文简体*/
 /*中文简体*/
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans SC";
-  font-style: normal;
-  font-weight: 300;
-  src: local("Noto Sans SC Light"), local("Noto Sans CJK SC Light"),
-    local("Source Han Sans CN Light");
-  /*url(../../font/NotoSansSC/NotoSansCJKsc-Light.otf) format('opentype'),
+	font-family: "Noto Sans SC";
+	font-style: normal;
+	font-weight: 300;
+	src: local("Noto Sans SC Light"), local("Noto Sans CJK SC Light"), local("Source Han Sans CN Light");
+	/*url(../../font/NotoSansSC/NotoSansCJKsc-Light.otf) format('opentype'),
     url(../../font/NotoSansSC/NotoSansSC-Light.woff) format('woff')*/
     url(../../font/NotoSansSC/NotoSansSC-Light.woff) format('woff')*/
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans SC";
-  font-style: normal;
-  font-weight: 400;
-  src: local("Noto Sans SC Regular"), local("Noto Sans CJK SC Regular"),
-    local("Source Han Sans CN Regular");
-  /*url(../../font/NotoSansSC/NotoSansCJKsc-Regular.otf) format('opentype'),
+	font-family: "Noto Sans SC";
+	font-style: normal;
+	font-weight: 400;
+	src: local("Noto Sans SC Regular"), local("Noto Sans CJK SC Regular"), local("Source Han Sans CN Regular");
+	/*url(../../font/NotoSansSC/NotoSansCJKsc-Regular.otf) format('opentype'),
     url(../../font/NotoSansSC/NotoSansSC-Regular.woff) format('woff');*/
     url(../../font/NotoSansSC/NotoSansSC-Regular.woff) format('woff');*/
-  font-display: fallback;
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans SC";
-  font-style: normal;
-  font-weight: 500;
-  src: local("Noto Sans SC Medium"), local("Noto Sans CJK SC Medium"),
-    local("Source Han Sans CN Medium");
-  /*url(../../font/NotoSansSC/NotoSansCJKsc-Medium.otf) format('opentype'),
+	font-family: "Noto Sans SC";
+	font-style: normal;
+	font-weight: 500;
+	src: local("Noto Sans SC Medium"), local("Noto Sans CJK SC Medium"), local("Source Han Sans CN Medium");
+	/*url(../../font/NotoSansSC/NotoSansCJKsc-Medium.otf) format('opentype'),
     url(../../font/NotoSansSC/NotoSansSC-Medium.woff) format('woff');*/
     url(../../font/NotoSansSC/NotoSansSC-Medium.woff) format('woff');*/
-  font-display: fallback;
+	font-display: fallback;
 }
 }
 
 
 @font-face {
 @font-face {
-  font-family: "Noto Sans SC";
-  font-style: normal;
-  font-weight: 700;
-  src: local("Noto Sans SC Bold"), local("Noto Sans CJK SC Bold"),
-    local("Source Han Sans CN Bold");
-  /*url(../../font/NotoSansSC/NotoSansCJKsc-Bold.otf) format('opentype'),
+	font-family: "Noto Sans SC";
+	font-style: normal;
+	font-weight: 700;
+	src: local("Noto Sans SC Bold"), local("Noto Sans CJK SC Bold"), local("Source Han Sans CN Bold");
+	/*url(../../font/NotoSansSC/NotoSansCJKsc-Bold.otf) format('opentype'),
     url(../../font/NotoSansSC/NotoSansSC-Bold.woff) format('woff');*/
     url(../../font/NotoSansSC/NotoSansSC-Bold.woff) format('woff');*/
-  font-display: fallback;
+	font-display: fallback;
 }
 }
 
 
 .font_ch {
 .font_ch {
-  font-family: "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
-  font-style: normal;
+	font-family: "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+	font-style: normal;
 }
 }
 
 
 .font_pali {
 .font_pali {
-  font-family: "Noto Sans", Arial, Verdana;
-  font-style: normal;
+	font-family: "Noto Sans", Arial, Verdana;
+	font-style: normal;
 }
 }
 
 
 .font_m {
 .font_m {
-  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
-  font-weight: 500;
-  font-style: normal;
+	font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+	font-weight: 500;
+	font-style: normal;
 }
 }
 
 
 .font_r {
 .font_r {
-  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
-  font-weight: 400;
-  font-style: normal;
+	font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+	font-weight: 400;
+	font-style: normal;
 }
 }
 
 
 .font_l {
 .font_l {
-  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
-  font-weight: 300;
-  font-style: normal;
+	font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+	font-weight: 300;
+	font-style: normal;
 }
 }
 
 
 .font_t {
 .font_t {
-  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
-  font-weight: 100;
-  font-style: normal;
+	font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+	font-weight: 100;
+	font-style: normal;
 }
 }

+ 28 - 12
app/pcdl/css/style.css

@@ -19,7 +19,7 @@
 }
 }
 
 
 body {
 body {
-	font-family: "Noto Sans", "Noto Sans SC", "Noto Sans TC", "Padauk", Arial, Verdana;
+	font-family: "Noto Sans", "Noto Sans SC", "Noto Sans TC", "Padauk", "ATaiThamKHNewV3-Normal", Arial, Verdana;
 	font-style: normal;
 	font-style: normal;
 	color: var(--main-color);
 	color: var(--main-color);
 	font-weight: 400;
 	font-weight: 400;
@@ -1857,8 +1857,10 @@ span:active {
 
 
 .edit_tool {
 .edit_tool {
 	flex: 0 0 100%;
 	flex: 0 0 100%;
-	display: block;
+	display: inline-flex;
 	margin: 0;
 	margin: 0;
+	width: 100%;
+	justify-content: space-evenly;
 }
 }
 
 
 .edit_tool .icon {
 .edit_tool .icon {
@@ -2685,32 +2687,46 @@ th {
 .tooltip {
 .tooltip {
 	position: relative;
 	position: relative;
 	display: inline;
 	display: inline;
-	cursor: help;
 }
 }
 
 
 .tooltip .tooltiptext {
 .tooltip .tooltiptext {
 	visibility: hidden;
 	visibility: hidden;
 	position: absolute;
 	position: absolute;
-	width: 100px;
+	min-width: 80px;
 	background-color: #555;
 	background-color: #555;
 	color: #fff;
 	color: #fff;
-	text-align: center;
-	padding: 5px 0;
+	/*text-align: center;*/
+	padding: 5px;
 	border-radius: 6px;
 	border-radius: 6px;
 	z-index: 1;
 	z-index: 1;
 	opacity: 0;
 	opacity: 0;
-	transition: opacity 0.6s;
-}
-
-.tooltip:hover {
-	color: red;
+	transition: all 0.6s;
+	white-space: nowrap;
 }
 }
 
 
 .tooltip:hover .tooltiptext {
 .tooltip:hover .tooltiptext {
 	visibility: visible;
 	visibility: visible;
 	opacity: 1;
 	opacity: 1;
 }
 }
-
+.tooltip-top {
+	top: -120%;
+	left: 50%;
+	margin-left: -40px;
+	transition: all 0.6s;
+}
+.tooltip:hover .tooltip-top {
+	top: -150%;
+}
+.tooltip .tooltip-top::after {
+	content: " ";
+	position: absolute;
+	top: 100%; /* 提示工具底部 */
+	left: 50%;
+	margin-left: -5px;
+	border-width: 5px;
+	border-style: solid;
+	border-color: #555 transparent transparent transparent;
+}
 .tooltip-bottom {
 .tooltip-bottom {
 	top: 100%;
 	top: 100%;
 	left: 50%;
 	left: 50%;

+ 1 - 1
app/pcdl/head_bar.php

@@ -336,7 +336,7 @@
 
 
 	.disable {
 	.disable {
 		color: var(--new-tool-content-disabled);
 		color: var(--new-tool-content-disabled);
-		cursor: default;
+		cursor: not-allowed;
 	}
 	}
 
 
 	@media screen and (min-width: 840px) {
 	@media screen and (min-width: 840px) {

+ 7 - 0
app/pcdl/html_head.php

@@ -100,6 +100,13 @@ if (isset($_GET["language"])) {
 	<link type="text/css" rel="stylesheet" href="../term/pali_sim_dlg.css" />
 	<link type="text/css" rel="stylesheet" href="../term/pali_sim_dlg.css" />
 	<script src="../term/related_para.js"></script>
 	<script src="../term/related_para.js"></script>
 
 
+	<script src="../widget/iframe_modal_win.js"></script>
+	<link type="text/css" rel="stylesheet" href="../widget/iframe_modal_win.css"/>
+
+	<script src="../commit/commit.js"></script>
+	<link type="text/css" rel="stylesheet" href="../commit/commit.css"/>
+
+
 	<script src="../inline_dict/inline_dict.js"></script>
 	<script src="../inline_dict/inline_dict.js"></script>
 
 
 	<script src="../widget/click_dropdown.js"></script>
 	<script src="../widget/click_dropdown.js"></script>

+ 58 - 63
app/pcdl/index.js

@@ -1,80 +1,75 @@
 function index_onload() {
 function index_onload() {
-    index_load_term_new();
-    index_load_collect_new();
-    index_load_course_new();
+	index_load_term_new();
+	index_load_collect_new();
+	index_load_course_new();
 }
 }
 
 
 function index_load_collect_new() {
 function index_load_collect_new() {
-    $.get(
-        "../article/list_new.php", {
-            begin: 0,
-            page: 4,
-        },
-        function(data, status) {
-            let arrCollectList = JSON.parse(data);
-            let html = "";
-            for (const iterator of arrCollectList.data) {
-                html += "<div class='card'>";
+	$.get(
+		"../article/list_new.php",
+		{
+			begin: 0,
+			page: 4,
+		},
+		function (data, status) {
+			let arrCollectList = JSON.parse(data);
+			let html = "";
+			for (const iterator of arrCollectList.data) {
+				html += "<div class='card'>";
 
 
-                html +=
-                    "<div class='card_state'>" + gLocal.gui.ongoing + "</div>";
+				html += "<div class='card_state'>" + gLocal.gui.ongoing + "</div>";
 
 
-                //!!!!!請協助補上頭像代碼!!!!!
-                html +=
-                    "<div style='padding:10px 10px 0 0;'><span class='head_img'></span></div>";
+				//!!!!!請協助補上頭像代碼!!!!!
+				html += "<div style='padding:10px 10px 0 0;'><span class='head_img'>";
+				html += iterator.username.nickname.slice(0, 1);
+				html += "</span></div>";
 
 
-                html += "<div class='article_right'>";
-                gLocal.gui.ongoing + "</div>";
+				html += "<div class='article_right'>";
+				gLocal.gui.ongoing + "</div>";
 
 
-                html +=
-                    "<div class='title'><a href='../article/?id=" +
-                    iterator.id +
-                    "'>" +
-                    iterator.title +
-                    "</a></div>";
+				html += "<div class='title'>";
+				html += "<a href='../article/?id=" + iterator.id + "'>" + iterator.title + "</a>";
+				html += "</div>";
 
 
+				html += "<div class='collect'>";
+				if (iterator.collect) {
+					html +=
+						"<a href='../article/?collect=" + iterator.collect.id + "'>" + iterator.collect.title + "</a>";
+				} else {
+					html += "unkow";
+				}
+				html += "</div>";
+				if (iterator.subtitle) {
+					html += "<div>" + iterator.subtitle + "</div>";
+				}
+				if (iterator.summary) {
+					html += "<div>" + iterator.summary + "</div>";
+				}
 
 
-                html += "<div class='collect'>";
-                if (iterator.collect) {
-                    html += "<a href='../article/?collect=" + iterator.collect.id + "'>" + iterator.collect.title + "</a>";
-                } else {
-                    html += "unkow";
-                }
-                html += "</div>";
-                if (iterator.subtitle) {
-                    html += "<div>" + iterator.subtitle + "</div>";
-                }
-                if (iterator.summary) {
-                    html += "<div>" + iterator.summary + "</div>";
-                }
+				html += "<div style='margin-top:1em;'>" + iterator.username.nickname + "</div>";
 
 
-
-                html += "<div style='margin-top:1em;'>" + iterator.username.nickname + "</div>";
-
-
-
-                html += "</div>";
-                html += "</div>";
-            }
-            $("#article_new").html(html);
-        }
-    );
+				html += "</div>";
+				html += "</div>";
+			}
+			$("#article_new").html(html);
+		}
+	);
 }
 }
 
 
 function index_load_term_new() {
 function index_load_term_new() {
-    $.get("../term/new.php", function(data, status) {
-        let xDiv = document.getElementById("pali_pedia");
-        if (xDiv) {
-            xDiv.innerHTML = data;
-        }
-    });
+	$.get("../term/new.php", function (data, status) {
+		let xDiv = document.getElementById("pali_pedia");
+		if (xDiv) {
+			xDiv.innerHTML = data;
+		}
+	});
 }
 }
 
 
 function index_load_course_new() {
 function index_load_course_new() {
-    $.get("../course/list_new.php", function(data, status) {
-        let xDiv = document.getElementById("course_list_new");
-        if (xDiv) {
-            xDiv.innerHTML = data;
-        }
-    });
-}
+	$.get("../course/list_new.php", function (data, status) {
+		let xDiv = document.getElementById("course_list_new");
+		if (xDiv) {
+			xDiv.innerHTML = data;
+		}
+	});
+}

File diff suppressed because it is too large
+ 14 - 0
app/public/images/svg.md


+ 235 - 0
app/public/js/Highstock-8.0.0/code/css/annotations/popup.css

@@ -0,0 +1,235 @@
+.highcharts-popup.highcharts-annotation-toolbar {
+  right: 10%;
+  left: auto;
+  height: 40px;
+  overflow: hidden;
+  padding-right: 40px;
+  width: auto;
+  min-width: 0;
+}
+
+.highcharts-popup.highcharts-annotation-toolbar button {
+  margin-top: 0px;
+}
+
+.highcharts-popup.highcharts-annotation-toolbar > span {
+  display: block;
+  float: left;
+  padding: 12px;
+}
+
+.highcharts-popup {
+  background-color: #fff;
+  color: #666;
+  display: none;
+  font-size: 0.876em;
+  max-height: 90%;
+  top: 5%;
+  left: 15%;
+  overflow-x: hidden;
+  overflow-y: auto;
+  width: 75%;
+  min-width: 300px;
+  max-width: 600px;
+  position: absolute;
+  z-index: 100;
+  -webkit-box-shadow: 0px 0px 8px 0px rgba(61, 61, 61, 0.3);
+  -moz-box-shadow: 0px 0px 8px 0px rgba(61, 61, 61, 0.3);
+  box-shadow: 0px 0px 8px 0px rgba(61, 61, 61, 0.3);
+}
+
+.highcharts-popup div, .highcharts-popup span {
+  box-sizing: content-box;
+}
+
+.highcharts-popup input, .highcharts-popup label, .highcharts-popup select {
+  clear: both;
+  float: left;
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.highcharts-popup input {
+  border: 1px solid #e6e6e6;
+  padding: 5px;
+  width: calc(100% - 12px);
+}
+
+.highcharts-popup-lhs-col, .highcharts-popup-rhs-col {
+  padding: 20px;
+  height: calc(100% - 84px);
+  /* 44px - padding, 40px - tabs*/
+  float: left;
+}
+
+.highcharts-popup-lhs-col.highcharts-popup-lhs-full {
+  width: calc(100% - 52px);
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 140px);
+  border: none;
+  padding: 0px 20px 20px 20px;
+}
+
+.highcharts-popup-lhs-col.highcharts-popup-lhs-full + .highcharts-popup-bottom-row {
+  width: calc(100% - 32px);
+}
+
+.highcharts-popup-lhs-col {
+  clear: both;
+  width: calc(30% - 44px);
+  border-right: 1px solid #e6e6e6;
+}
+
+.highcharts-popup-bottom-row {
+  float: left;
+  padding: 0px 20px;
+  width: calc(100% - 40px);
+}
+
+.highcharts-popup-rhs-col {
+  width: calc(70% - 40px);
+}
+
+.highcharts-popup-rhs-col-wrapper {
+  float: left;
+  width: calc(100% - 20px);
+  padding-right: 20px;
+  height: calc(100% - 40px);
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.highcharts-popup-rhs-col-wrapper h3 {
+  margin-top: 0px;
+  padding-bottom: 0px;
+}
+
+.highcharts-bindings-wrapper ul.highcharts-indicator-list,
+.highcharts-indicator-list {
+  float: left;
+  color: #666;
+  height: 100%;
+  width: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
+  margin: 0px;
+  padding: 0px;
+}
+
+.highcharts-indicator-list li {
+  cursor: pointer;
+  padding: 0px 0px 5px 0px;
+  margin: 0px;
+  width: 100%;
+  height: auto;
+  overflow: hidden;
+  word-break: break-all;
+}
+
+.highcharts-indicator-list li:hover {
+  background-color: #e6ebf5;
+}
+
+.highcharts-tab-item {
+  background-color: #f7f7f7;
+  cursor: pointer;
+  display: block;
+  float: left;
+  padding: 10px;
+  height: 20px;
+}
+
+.highcharts-tab-item.highcharts-tab-item-active {
+  background-color: #e6ebf5;
+}
+
+.highcharts-tab-item-content {
+  display: none;
+  float: left;
+  height: 100%;
+  overflow: hidden;
+  width: 100%;
+  border-top: 1px solid #e6e6e6;
+}
+
+.highcharts-tab-item-show {
+  display: block;
+}
+
+.highcharts-popup-close {
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+  width: 20px;
+  height: 20px;
+  cursor: pointer;
+  position: absolute;
+  padding: 10px;
+  top: 0%;
+  right: 0%;
+  color: #333333;
+}
+
+.highcharts-popup-close:hover,
+.highcharts-popup button:hover,
+.highcharts-popup button.highcharts-annotation-edit-button:hover,
+.highcharts-popup button.highcharts-annotation-remove-button:hover {
+  background-color: #e6ebf5;
+}
+
+.highcharts-popup button {
+  float: right;
+  border: none;
+  background: #f7f7f7;
+  color: #666;
+  margin-left: 5px;
+  margin-top: 12px;
+}
+
+.highcharts-popup button:first-child {
+  margin-left: 0;
+}
+
+.highcharts-tab-disabled {
+  color: #ccc;
+}
+
+/* annotation edit small popup */
+.highcharts-popup button.highcharts-annotation-edit-button,
+.highcharts-popup button.highcharts-annotation-remove-button {
+  width: 20px;
+  height: 40px;
+  padding: 20px;
+}
+
+.highcharts-popup button.highcharts-annotation-edit-button {
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+  text-indent: -9999px;
+}
+
+.highcharts-popup button.highcharts-annotation-remove-button {
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+  text-indent: -9999px;
+}
+
+.highcharts-popup .highcharts-annotation-title {
+  display: block;
+  float: left;
+  font-size: 1.2em;
+  font-weight: bold;
+  margin-bottom: 15px;
+  width: 100%;
+}
+
+.highcharts-popup .highcharts-popup-main-title {
+  border-bottom: 1px solid #e6e6e6;
+  margin: 0px 0px 20px 0px;
+  padding: 8px 0px 6px 20px;
+}
+
+.highcharts-indicator-title {
+  float: left;
+  padding-bottom: 15px;
+}

+ 239 - 0
app/public/js/Highstock-8.0.0/code/css/annotations/popup.scss

@@ -0,0 +1,239 @@
+// Colors for buttons.
+$button-background-color: #f7f7f7;
+$button-hover-color: #e6ebf5;
+
+
+.highcharts-popup.highcharts-annotation-toolbar {
+  right: 10%;
+  left: auto;
+  height: 40px;
+  overflow: hidden;
+  padding-right: 40px;
+  width: auto;
+  min-width: 0;
+}
+
+.highcharts-popup.highcharts-annotation-toolbar button {
+  margin-top:0px;
+}
+
+.highcharts-popup.highcharts-annotation-toolbar > span {
+  display:block;
+  float:left;
+  padding: 12px;
+}
+
+
+.highcharts-popup {
+  background-color: #fff;
+  color: #666;
+  display: none;
+  font-size: 0.876em;
+  max-height: 90%;
+  top: 5%;
+  left: 15%;
+  overflow-x: hidden;
+  overflow-y: auto;
+  width: 75%;
+  min-width: 300px;
+  max-width: 600px;
+  position: absolute;
+  z-index: 100;
+  -webkit-box-shadow: 0px 0px 8px 0px rgba(61,61,61,0.3);
+  -moz-box-shadow: 0px 0px 8px 0px rgba(61,61,61,0.3);
+  box-shadow: 0px 0px 8px 0px rgba(61,61,61,0.3);
+}
+
+.highcharts-popup div, .highcharts-popup span {
+  box-sizing: content-box;
+}
+
+.highcharts-popup input, .highcharts-popup label, .highcharts-popup select {
+  clear: both;
+  float: left;
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.highcharts-popup input {
+  border: 1px solid #e6e6e6;
+  padding: 5px;
+  width: calc(100% - 12px);
+}
+
+.highcharts-popup-lhs-col, .highcharts-popup-rhs-col {
+  padding: 20px;
+  height: calc(100% - 84px); /* 44px - padding, 40px - tabs*/
+  float: left;
+}
+
+.highcharts-popup-lhs-col.highcharts-popup-lhs-full { 
+  width: calc(100% - 52px);
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 140px);
+  border: none;
+  padding: 0px 20px 20px 20px;
+}
+
+.highcharts-popup-lhs-col.highcharts-popup-lhs-full + .highcharts-popup-bottom-row {
+  width: calc(100% - 32px);
+}
+
+.highcharts-popup-lhs-col {
+  clear: both;
+  width: calc(30% - 44px);
+  border-right: 1px solid #e6e6e6;
+}
+
+.highcharts-popup-bottom-row {
+  float: left;
+  padding: 0px 20px;
+  width: calc(100% - 40px);
+}
+
+.highcharts-popup-rhs-col {
+  width: calc(70% - 40px);
+}
+
+.highcharts-popup-rhs-col-wrapper {
+  float: left;
+  width: calc(100% - 20px);
+  padding-right: 20px;
+  height: calc(100% - 40px);
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.highcharts-popup-rhs-col-wrapper h3 {
+  margin-top:0px;
+  padding-bottom:0px;
+}
+
+.highcharts-bindings-wrapper ul.highcharts-indicator-list,
+.highcharts-indicator-list {
+  float: left;
+  color: #666;
+  height: 100%;
+  width: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
+  margin: 0px;
+  padding: 0px;
+}
+
+.highcharts-indicator-list li {
+  cursor: pointer;
+  padding: 0px 0px 5px 0px;
+  margin: 0px;
+  width: 100%;
+  height: auto;
+  overflow: hidden;
+  word-break: break-all;
+}
+
+.highcharts-indicator-list li:hover {
+  background-color: $button-hover-color;
+}
+
+.highcharts-tab-item {
+  background-color: $button-background-color;
+  cursor: pointer;
+  display: block;
+  float:left;
+  padding: 10px;
+  height: 20px;
+}
+
+.highcharts-tab-item.highcharts-tab-item-active {
+  background-color: $button-hover-color;
+}
+
+.highcharts-tab-item-content {
+  display: none; 
+  float: left;
+  height: 100%;
+  overflow: hidden;
+  width: 100%;
+  border-top: 1px solid #e6e6e6;
+}
+
+.highcharts-tab-item-show {
+  display: block;
+}
+
+.highcharts-popup-close {
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+  width: 20px;
+  height: 20px;
+  cursor: pointer;
+  position: absolute;
+  padding: 10px;
+  top: 0%;
+  right: 0%;
+  color: #333333;
+}
+
+.highcharts-popup-close:hover,
+.highcharts-popup button:hover,
+.highcharts-popup button.highcharts-annotation-edit-button:hover,
+.highcharts-popup button.highcharts-annotation-remove-button:hover {
+  background-color: $button-hover-color;
+}
+
+.highcharts-popup button {
+  float: right;
+  border: none;
+  background: $button-background-color;
+  color: #666;
+  margin-left:5px;
+  margin-top:12px;
+}
+.highcharts-popup button:first-child {
+  margin-left: 0;
+}
+
+.highcharts-tab-disabled {
+  color: #ccc;
+}
+
+/* annotation edit small popup */
+.highcharts-popup button.highcharts-annotation-edit-button,
+.highcharts-popup button.highcharts-annotation-remove-button {
+  width: 20px;
+  height: 40px;
+  padding: 20px;
+}
+
+.highcharts-popup button.highcharts-annotation-edit-button {
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+  text-indent: -9999px;
+}
+
+.highcharts-popup button.highcharts-annotation-remove-button {
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+  text-indent: -9999px;
+}
+
+.highcharts-popup .highcharts-annotation-title {
+  display: block;
+  float: left;
+  font-size: 1.2em;
+  font-weight: bold;
+  margin-bottom: 15px;
+  width: 100%;
+}
+
+.highcharts-popup .highcharts-popup-main-title {
+  border-bottom: 1px solid #e6e6e6;
+  margin: 0px 0px 20px 0px;
+  padding: 8px 0px 6px 20px;
+}
+
+.highcharts-indicator-title {
+  float: left;
+  padding-bottom: 15px;
+}

+ 926 - 0
app/public/js/Highstock-8.0.0/code/css/highcharts.css

@@ -0,0 +1,926 @@
+/**
+ * @license Highcharts
+ *
+ * (c) 2009-2016 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+.highcharts-container {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+  text-align: left;
+  line-height: normal;
+  z-index: 0;
+  /* #1072 */
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+  font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif;
+  font-size: 12px;
+}
+
+.highcharts-root {
+  display: block;
+}
+
+.highcharts-root text {
+  stroke-width: 0;
+}
+
+.highcharts-strong {
+  font-weight: bold;
+}
+
+.highcharts-emphasized {
+  font-style: italic;
+}
+
+.highcharts-anchor {
+  cursor: pointer;
+}
+
+.highcharts-background {
+  fill: #ffffff;
+}
+
+.highcharts-plot-border, .highcharts-plot-background {
+  fill: none;
+}
+
+.highcharts-label-box {
+  fill: none;
+}
+
+.highcharts-button-box {
+  fill: inherit;
+}
+
+.highcharts-tracker-line {
+  stroke-linejoin: round;
+  stroke: rgba(192, 192, 192, 0.0001);
+  stroke-width: 22;
+  fill: none;
+}
+
+.highcharts-tracker-area {
+  fill: rgba(192, 192, 192, 0.0001);
+  stroke-width: 0;
+}
+
+/* Titles */
+.highcharts-title {
+  fill: #333333;
+  font-size: 1.5em;
+}
+
+.highcharts-subtitle {
+  fill: #666666;
+}
+
+/* Axes */
+.highcharts-axis-line {
+  fill: none;
+  stroke: #ccd6eb;
+}
+
+.highcharts-yaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-axis-title {
+  fill: #666666;
+}
+
+.highcharts-axis-labels {
+  fill: #666666;
+  cursor: default;
+  font-size: 0.9em;
+}
+
+.highcharts-grid-line {
+  fill: none;
+  stroke: #e6e6e6;
+}
+
+.highcharts-xaxis-grid .highcharts-grid-line {
+  stroke-width: 0px;
+}
+
+.highcharts-tick {
+  stroke: #ccd6eb;
+}
+
+.highcharts-yaxis .highcharts-tick {
+  stroke-width: 0;
+}
+
+.highcharts-minor-grid-line {
+  stroke: #f2f2f2;
+}
+
+.highcharts-crosshair-thin {
+  stroke-width: 1px;
+  stroke: #cccccc;
+}
+
+.highcharts-crosshair-category {
+  stroke: #ccd6eb;
+  stroke-opacity: 0.25;
+}
+
+/* Credits */
+.highcharts-credits {
+  cursor: pointer;
+  fill: #999999;
+  font-size: 0.7em;
+  transition: fill 250ms, font-size 250ms;
+}
+
+.highcharts-credits:hover {
+  fill: black;
+  font-size: 1em;
+}
+
+/* Tooltip */
+.highcharts-tooltip {
+  cursor: default;
+  pointer-events: none;
+  white-space: nowrap;
+  transition: stroke 150ms;
+}
+
+.highcharts-tooltip text {
+  fill: #333333;
+}
+
+.highcharts-tooltip .highcharts-header {
+  font-size: 0.85em;
+}
+
+.highcharts-tooltip-box {
+  stroke-width: 1px;
+  fill: #f7f7f7;
+  fill-opacity: 0.85;
+}
+
+.highcharts-tooltip-box .highcharts-label-box {
+  fill: #f7f7f7;
+  fill-opacity: 0.85;
+}
+
+div.highcharts-tooltip {
+  filter: none;
+}
+
+.highcharts-selection-marker {
+  fill: #335cad;
+  fill-opacity: 0.25;
+}
+
+.highcharts-graph {
+  fill: none;
+  stroke-width: 2px;
+  stroke-linecap: round;
+  stroke-linejoin: round;
+}
+
+.highcharts-state-hover .highcharts-graph {
+  stroke-width: 3;
+}
+
+.highcharts-point-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-series-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-state-hover path {
+  transition: stroke-width 50ms;
+  /* quick in */
+}
+
+.highcharts-state-normal path {
+  transition: stroke-width 250ms;
+  /* slow out */
+}
+
+/* Legend hover affects points and series */
+g.highcharts-series,
+.highcharts-point,
+.highcharts-markers,
+.highcharts-data-labels {
+  transition: opacity 250ms;
+}
+
+.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
+.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
+.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
+.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
+  opacity: 0.2;
+}
+
+/* Series options */
+/* Default colors */
+.highcharts-color-0 {
+  fill: #7cb5ec;
+  stroke: #7cb5ec;
+}
+
+.highcharts-color-1 {
+  fill: #434348;
+  stroke: #434348;
+}
+
+.highcharts-color-2 {
+  fill: #90ed7d;
+  stroke: #90ed7d;
+}
+
+.highcharts-color-3 {
+  fill: #f7a35c;
+  stroke: #f7a35c;
+}
+
+.highcharts-color-4 {
+  fill: #8085e9;
+  stroke: #8085e9;
+}
+
+.highcharts-color-5 {
+  fill: #f15c80;
+  stroke: #f15c80;
+}
+
+.highcharts-color-6 {
+  fill: #e4d354;
+  stroke: #e4d354;
+}
+
+.highcharts-color-7 {
+  fill: #2b908f;
+  stroke: #2b908f;
+}
+
+.highcharts-color-8 {
+  fill: #f45b5b;
+  stroke: #f45b5b;
+}
+
+.highcharts-color-9 {
+  fill: #91e8e1;
+  stroke: #91e8e1;
+}
+
+.highcharts-area {
+  fill-opacity: 0.75;
+  stroke-width: 0;
+}
+
+.highcharts-markers {
+  stroke-width: 1px;
+  stroke: #ffffff;
+}
+
+.highcharts-point {
+  stroke-width: 1px;
+}
+
+.highcharts-dense-data .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-data-label {
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+.highcharts-data-label-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-data-label text, text.highcharts-data-label {
+  fill: #333333;
+}
+
+.highcharts-data-label-connector {
+  fill: none;
+}
+
+.highcharts-halo {
+  fill-opacity: 0.25;
+  stroke-width: 0;
+}
+
+.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
+.highcharts-markers .highcharts-point-select {
+  fill: #cccccc;
+  stroke: #000000;
+}
+
+.highcharts-column-series rect.highcharts-point {
+  stroke: #ffffff;
+}
+
+.highcharts-column-series .highcharts-point {
+  transition: fill-opacity 250ms;
+}
+
+.highcharts-column-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pie-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #ffffff;
+}
+
+.highcharts-pie-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #ffffff;
+}
+
+.highcharts-funnel-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-pyramid-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #ffffff;
+}
+
+.highcharts-pyramid-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pyramid-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-solidgauge-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-treemap-series .highcharts-point {
+  stroke-width: 1px;
+  stroke: #e6e6e6;
+  transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
+}
+
+.highcharts-treemap-series .highcharts-point-hover {
+  stroke: #999999;
+  transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
+}
+
+.highcharts-treemap-series .highcharts-above-level {
+  display: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node {
+  fill: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive {
+  fill-opacity: 0.15;
+  cursor: pointer;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
+  fill-opacity: 0.75;
+}
+
+.highcharts-vector-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+.highcharts-windbarb-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+/* Dumbbell/lollipop connector */
+.highcharts-lollipop-stem {
+  stroke: #000000;
+}
+
+/* Legend */
+.highcharts-legend-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item > text {
+  fill: #333333;
+  font-weight: bold;
+  font-size: 1em;
+  cursor: pointer;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item:hover text {
+  fill: #000000;
+}
+
+.highcharts-legend-item-hidden * {
+  fill: #cccccc !important;
+  stroke: #cccccc !important;
+  transition: fill 250ms;
+}
+
+.highcharts-legend-nav-active {
+  fill: #003399;
+  cursor: pointer;
+}
+
+.highcharts-legend-nav-inactive {
+  fill: #cccccc;
+}
+
+circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive {
+  /* tracker */
+  fill: rgba(192, 192, 192, 0.0001);
+}
+
+.highcharts-legend-title-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+/* Bubble legend */
+.highcharts-bubble-legend-symbol {
+  stroke-width: 2;
+  fill-opacity: 0.5;
+}
+
+.highcharts-bubble-legend-connectors {
+  stroke-width: 1;
+}
+
+.highcharts-bubble-legend-labels {
+  fill: #333333;
+}
+
+/* Loading */
+.highcharts-loading {
+  position: absolute;
+  background-color: #ffffff;
+  opacity: 0.5;
+  text-align: center;
+  z-index: 10;
+  transition: opacity 250ms;
+}
+
+.highcharts-loading-hidden {
+  height: 0 !important;
+  opacity: 0;
+  overflow: hidden;
+  transition: opacity 250ms, height 250ms step-end;
+}
+
+.highcharts-loading-inner {
+  font-weight: bold;
+  position: relative;
+  top: 45%;
+}
+
+/* Plot bands and polar pane backgrounds */
+.highcharts-plot-band, .highcharts-pane {
+  fill: #000000;
+  fill-opacity: 0.05;
+}
+
+.highcharts-plot-line {
+  fill: none;
+  stroke: #999999;
+  stroke-width: 1px;
+}
+
+/* Highcharts More and modules */
+.highcharts-boxplot-box {
+  fill: #ffffff;
+}
+
+.highcharts-boxplot-median {
+  stroke-width: 2px;
+}
+
+.highcharts-bubble-series .highcharts-point {
+  fill-opacity: 0.5;
+}
+
+.highcharts-errorbar-series .highcharts-point {
+  stroke: #000000;
+}
+
+.highcharts-gauge-series .highcharts-data-label-box {
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-gauge-series .highcharts-dial {
+  fill: #000000;
+  stroke-width: 0;
+}
+
+.highcharts-polygon-series .highcharts-graph {
+  fill: inherit;
+  stroke-width: 0;
+}
+
+.highcharts-waterfall-series .highcharts-graph {
+  stroke: #333333;
+  stroke-dasharray: 1, 3;
+}
+
+.highcharts-sankey-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-sankey-series .highcharts-link {
+  transition: fill 250ms, fill-opacity 250ms;
+  fill-opacity: 0.5;
+}
+
+.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
+  transition: fill 50ms, fill-opacity 50ms;
+  fill-opacity: 1;
+}
+
+.highcharts-venn-series .highcharts-point {
+  fill-opacity: 0.75;
+  stroke: #cccccc;
+  transition: stroke 250ms, fill-opacity 250ms;
+}
+
+.highcharts-venn-series .highcharts-point-hover {
+  fill-opacity: 1;
+  stroke: #cccccc;
+}
+
+/* Highstock */
+.highcharts-navigator-mask-outside {
+  fill-opacity: 0;
+}
+
+.highcharts-navigator-mask-inside {
+  fill: #6685c2;
+  /* navigator.maskFill option */
+  fill-opacity: 0.25;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-outline {
+  stroke: #cccccc;
+  fill: none;
+}
+
+.highcharts-navigator-handle {
+  stroke: #cccccc;
+  fill: #f2f2f2;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-series {
+  fill: #335cad;
+  stroke: #335cad;
+}
+
+.highcharts-navigator-series .highcharts-graph {
+  stroke-width: 1px;
+}
+
+.highcharts-navigator-series .highcharts-area {
+  fill-opacity: 0.05;
+}
+
+.highcharts-navigator-xaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke-width: 1px;
+  stroke: #e6e6e6;
+}
+
+.highcharts-navigator-xaxis.highcharts-axis-labels {
+  fill: #999999;
+}
+
+.highcharts-navigator-yaxis .highcharts-grid-line {
+  stroke-width: 0;
+}
+
+.highcharts-scrollbar-thumb {
+  fill: #cccccc;
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-button {
+  fill: #e6e6e6;
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-arrow {
+  fill: #666666;
+}
+
+.highcharts-scrollbar-rifles {
+  stroke: #666666;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-track {
+  fill: #f2f2f2;
+  stroke: #f2f2f2;
+  stroke-width: 1px;
+}
+
+.highcharts-button {
+  fill: #f7f7f7;
+  stroke: #cccccc;
+  cursor: default;
+  stroke-width: 1px;
+  transition: fill 250ms;
+}
+
+.highcharts-button text {
+  fill: #333333;
+}
+
+.highcharts-button-hover {
+  transition: fill 0ms;
+  fill: #e6e6e6;
+  stroke: #cccccc;
+}
+
+.highcharts-button-hover text {
+  fill: #333333;
+}
+
+.highcharts-button-pressed {
+  font-weight: bold;
+  fill: #e6ebf5;
+  stroke: #cccccc;
+}
+
+.highcharts-button-pressed text {
+  fill: #333333;
+  font-weight: bold;
+}
+
+.highcharts-button-disabled text {
+  fill: #333333;
+}
+
+.highcharts-range-selector-buttons .highcharts-button {
+  stroke-width: 0px;
+}
+
+.highcharts-range-label rect {
+  fill: none;
+}
+
+.highcharts-range-label text {
+  fill: #666666;
+}
+
+.highcharts-range-input rect {
+  fill: none;
+}
+
+.highcharts-range-input text {
+  fill: #333333;
+}
+
+.highcharts-range-input {
+  stroke-width: 1px;
+  stroke: #cccccc;
+}
+
+input.highcharts-range-selector {
+  position: absolute;
+  border: 0;
+  width: 1px;
+  /* Chrome needs a pixel to see it */
+  height: 1px;
+  padding: 0;
+  text-align: center;
+  left: -9em;
+  /* #4798 */
+}
+
+.highcharts-crosshair-label text {
+  fill: #ffffff;
+  font-size: 1.1em;
+}
+
+.highcharts-crosshair-label .highcharts-label-box {
+  fill: inherit;
+}
+
+.highcharts-candlestick-series .highcharts-point {
+  stroke: #000000;
+  stroke-width: 1px;
+}
+
+.highcharts-candlestick-series .highcharts-point-up {
+  fill: #ffffff;
+}
+
+.highcharts-ohlc-series .highcharts-point-hover {
+  stroke-width: 3px;
+}
+
+.highcharts-flags-series .highcharts-point .highcharts-label-box {
+  stroke: #999999;
+  fill: #ffffff;
+  transition: fill 250ms;
+}
+
+.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
+  stroke: #000000;
+  fill: #ccd6eb;
+}
+
+.highcharts-flags-series .highcharts-point text {
+  fill: #000000;
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+/* Highmaps */
+.highcharts-map-series .highcharts-point {
+  transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
+  stroke: #cccccc;
+}
+
+.highcharts-map-series .highcharts-point-hover {
+  transition: fill 0ms, fill-opacity 0ms;
+  fill-opacity: 0.5;
+  stroke-width: 2px;
+}
+
+.highcharts-mapline-series .highcharts-point {
+  fill: none;
+}
+
+.highcharts-heatmap-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-map-navigation {
+  font-size: 1.3em;
+  font-weight: bold;
+  text-align: center;
+}
+
+.highcharts-coloraxis {
+  stroke-width: 0;
+}
+
+.highcharts-coloraxis-marker {
+  fill: #999999;
+}
+
+.highcharts-null-point {
+  fill: #f7f7f7;
+}
+
+/* 3d charts */
+.highcharts-3d-frame {
+  fill: transparent;
+}
+
+/* Exporting module */
+.highcharts-contextbutton {
+  fill: #ffffff;
+  /* needed to capture hover */
+  stroke: none;
+  stroke-linecap: round;
+}
+
+.highcharts-contextbutton:hover {
+  fill: #e6e6e6;
+  stroke: #e6e6e6;
+}
+
+.highcharts-button-symbol {
+  stroke: #666666;
+  stroke-width: 3px;
+}
+
+.highcharts-menu {
+  border: 1px solid #999999;
+  background: #ffffff;
+  padding: 5px 0;
+  box-shadow: 3px 3px 10px #888;
+}
+
+.highcharts-menu-item {
+  padding: 0.5em 1em;
+  background: none;
+  color: #333333;
+  cursor: pointer;
+  transition: background 250ms, color 250ms;
+}
+
+.highcharts-menu-item:hover {
+  background: #335cad;
+  color: #ffffff;
+}
+
+/* Drilldown module */
+.highcharts-drilldown-point {
+  cursor: pointer;
+}
+
+.highcharts-drilldown-data-label text,
+text.highcharts-drilldown-data-label,
+.highcharts-drilldown-axis-label {
+  cursor: pointer;
+  fill: #003399;
+  font-weight: bold;
+  text-decoration: underline;
+}
+
+/* No-data module */
+.highcharts-no-data text {
+  font-weight: bold;
+  font-size: 12px;
+  fill: #666666;
+}
+
+/* Drag-panes module */
+.highcharts-axis-resizer {
+  cursor: ns-resize;
+  stroke: black;
+  stroke-width: 2px;
+}
+
+/* Bullet type series */
+.highcharts-bullet-target {
+  stroke-width: 0;
+}
+
+/* Lineargauge type series */
+.highcharts-lineargauge-target {
+  stroke-width: 1px;
+  stroke: #333333;
+}
+
+.highcharts-lineargauge-target-line {
+  stroke-width: 1px;
+  stroke: #333333;
+}
+
+/* Annotations module */
+.highcharts-annotation-label-box {
+  stroke-width: 1px;
+  stroke: #000000;
+  fill: #000000;
+  fill-opacity: 0.75;
+}
+
+.highcharts-annotation-label text {
+  fill: #e6e6e6;
+}
+
+/* Gantt */
+.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
+  cursor: pointer;
+}
+
+.highcharts-point-connecting-path {
+  fill: none;
+}
+
+.highcharts-grid-axis .highcharts-tick {
+  stroke-width: 1px;
+}
+
+.highcharts-grid-axis .highcharts-axis-line {
+  stroke-width: 1px;
+}

+ 818 - 0
app/public/js/Highstock-8.0.0/code/css/highcharts.scss

@@ -0,0 +1,818 @@
+/**
+ * @license Highcharts
+ *
+ * (c) 2009-2016 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+// Colors for data series and points.
+$colors: #7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1 !default;
+
+// Chart background, point stroke for markers and columns etc
+$background-color: #ffffff !default;
+
+// Neutral colors, grayscale by default. The default colors are defined by mixing the
+// background-color with neutral, with a weight corresponding to the number in the name.
+$neutral-color-100: #000000 !default; // Strong text.
+$neutral-color-80: #333333 !default; // Main text and some strokes.
+$neutral-color-60: #666666 !default; // Axis labels, axis title, connector fallback.
+$neutral-color-40: #999999 !default; // Credits text, export menu stroke.
+$neutral-color-20: #cccccc !default; // Disabled texts, button strokes, crosshair etc.
+$neutral-color-10: #e6e6e6 !default; // Grid lines etc.
+$neutral-color-5: #f2f2f2 !default; // Minor grid lines etc.
+$neutral-color-3: #f7f7f7 !default; // Tooltip backgroud, button fills, map null points.
+
+// Colored, shades of blue by default
+$highlight-color-100: #003399 !default; // Drilldown clickable labels, color axis max color.
+$highlight-color-80: #335cad !default; // Selection marker, menu hover, button hover, chart border, navigator series.
+$highlight-color-60: #6685c2 !default; // Navigator mask fill.
+$highlight-color-20: #ccd6eb !default; // Ticks and axis line.
+$highlight-color-10: #e6ebf5 !default; // Pressed button, color axis min color.
+
+// Fonts
+$font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif !default;
+$title-font-size: 1.5em !default;
+$subtitle-font-size: 1em !default;
+$legend-font-size: 1em !default;
+$axis-labels-font-size: 0.9em !default;
+
+// Tooltip
+$tooltip-border: 1px !default;
+$tooltip-background: $neutral-color-3 !default;
+
+// Axes
+$xaxis-grid-line: 0px !default;
+
+// Range-selector
+$range-selector-button-border: 0px !default;
+$range-selector-input-text: $neutral-color-80 !default;
+$range-selector-input-border: $neutral-color-20 !default;
+
+// Data-labels
+$data-label-color: $neutral-color-80 !default;
+
+// Buttons
+$context-button-background: $background-color !default;
+
+$highcharts-button-background: $neutral-color-3 !default;
+$highcharts-button-border: $neutral-color-20 !default;
+$highcharts-button-text:  $neutral-color-80 !default;
+
+$highcharts-button-pressed-background: $highlight-color-10 !default;
+$highcharts-button-pressed-border: $neutral-color-20 !default;
+$highcharts-button-pressed-text:  $neutral-color-80 !default;
+
+$highcharts-button-hover-background: $neutral-color-10 !default;
+$highcharts-button-hover-border: $neutral-color-20 !default;
+$highcharts-button-hover-text:  $neutral-color-80 !default;
+
+// Navigator
+$navigator-series-fill: $highlight-color-80 !default;
+$navigator-series-border: $highlight-color-80 !default;
+
+// Scrollbar
+$scrollbar-track-background: $neutral-color-5 !default;
+$scrollbar-track-border: $neutral-color-5 !default;
+
+// Indicators
+$indicator-positive-line: #06B535;
+$indicator-negative-line: #F21313;
+
+.highcharts-container {
+    position: relative;
+    overflow: hidden;
+    width: 100%;
+    height: 100%;
+    text-align: left;
+    line-height: normal;
+    z-index: 0; /* #1072 */
+    -webkit-tap-highlight-color: rgba(0,0,0,0);
+    font-family: $font-family;
+    font-size: 12px;
+}
+.highcharts-root {
+    display: block;
+}
+.highcharts-root text {
+    stroke-width: 0;
+}
+.highcharts-strong {
+    font-weight: bold;
+}
+.highcharts-emphasized {
+    font-style: italic;
+}
+.highcharts-anchor {
+    cursor: pointer;
+}
+.highcharts-background {
+    fill: $background-color;
+}
+.highcharts-plot-border, .highcharts-plot-background {
+    fill: none;
+}
+.highcharts-label-box {
+    fill: none;
+}
+.highcharts-button-box {
+    fill: inherit;
+}
+.highcharts-tracker-line {
+    stroke-linejoin: round;
+    stroke: rgba(192, 192, 192, 0.0001);
+    stroke-width: 22;
+    fill: none;
+}
+.highcharts-tracker-area {
+    fill: rgba(192, 192, 192, 0.0001);
+    stroke-width: 0;
+}
+
+/* Titles */
+.highcharts-title {
+    fill: $neutral-color-80;
+    font-size: $title-font-size;
+}
+.highcharts-subtitle {
+    fill: $neutral-color-60;
+}
+
+/* Axes */
+.highcharts-axis-line {
+    fill: none;
+    stroke: $highlight-color-20;
+}
+.highcharts-yaxis .highcharts-axis-line {
+    stroke-width: 0;
+}
+.highcharts-axis-title {
+    fill: $neutral-color-60;
+}
+.highcharts-axis-labels {
+    fill: $neutral-color-60;
+    cursor: default;
+    font-size: $axis-labels-font-size;
+}
+.highcharts-grid-line {
+    fill: none;
+    stroke: $neutral-color-10;
+}
+.highcharts-xaxis-grid .highcharts-grid-line {
+    stroke-width: $xaxis-grid-line;
+}
+.highcharts-tick {
+    stroke: $highlight-color-20;
+}
+.highcharts-yaxis .highcharts-tick {
+    stroke-width: 0;
+}
+.highcharts-minor-grid-line {
+    stroke: $neutral-color-5;
+}
+.highcharts-crosshair-thin {
+    stroke-width: 1px;
+    stroke: $neutral-color-20;
+}
+.highcharts-crosshair-category {
+    stroke: $highlight-color-20;
+    stroke-opacity: 0.25;
+}
+
+
+/* Credits */
+.highcharts-credits {
+    cursor: pointer;
+    fill: $neutral-color-40;
+    font-size: 0.7em;
+    transition: fill 250ms, font-size 250ms;
+}
+.highcharts-credits:hover {
+    fill: black;
+    font-size: 1em;
+}
+
+/* Tooltip */
+.highcharts-tooltip {
+    cursor: default;
+    pointer-events: none;
+    white-space: nowrap;
+    transition: stroke 150ms;
+}
+.highcharts-tooltip text {
+    fill: $neutral-color-80;
+}
+.highcharts-tooltip .highcharts-header {
+    font-size: 0.85em;
+}
+.highcharts-tooltip-box {
+    stroke-width: $tooltip-border;
+    fill: $tooltip-background;
+    fill-opacity: 0.85;
+}
+.highcharts-tooltip-box .highcharts-label-box {
+    fill: $tooltip-background;
+    fill-opacity: 0.85;
+}
+div.highcharts-tooltip {
+    filter: none;
+}
+
+.highcharts-selection-marker {
+    fill: $highlight-color-80;
+    fill-opacity: 0.25;
+}
+
+.highcharts-graph {
+    fill: none;
+    stroke-width: 2px;
+    stroke-linecap: round;
+    stroke-linejoin: round;
+}
+.highcharts-state-hover .highcharts-graph {
+    stroke-width: 3;
+}
+
+.highcharts-point-inactive {
+    opacity: 0.2;
+    transition: opacity 50ms; /* quick in */
+}
+
+.highcharts-series-inactive {
+    opacity: 0.2;
+    transition: opacity 50ms; /* quick in */
+}
+
+.highcharts-state-hover path {
+    transition: stroke-width 50ms; /* quick in */
+}
+.highcharts-state-normal path {
+    transition: stroke-width 250ms; /* slow out */
+}
+/* Legend hover affects points and series */
+g.highcharts-series,
+.highcharts-point,
+.highcharts-markers,
+.highcharts-data-labels {
+    transition: opacity 250ms;
+}
+.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
+.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
+.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
+.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
+    opacity: 0.2;
+}
+
+/* Series options */
+
+/* Default colors */
+@for $i from 1 through length($colors) {
+  $color: nth($colors, $i);
+  .highcharts-color-#{$i - 1} {
+    fill: $color;
+    stroke: $color;
+  }
+}
+
+.highcharts-area {
+    fill-opacity: 0.75;
+    stroke-width: 0;
+}
+.highcharts-markers {
+    stroke-width: 1px;
+    stroke: $background-color;
+}
+.highcharts-point {
+    stroke-width: 1px;
+}
+.highcharts-dense-data .highcharts-point {
+    stroke-width: 0;
+}
+
+.highcharts-data-label {
+    font-size: 0.9em;
+    font-weight: bold;
+}
+.highcharts-data-label-box {
+    fill: none;
+    stroke-width: 0;
+}
+.highcharts-data-label text, text.highcharts-data-label {
+    fill: $data-label-color;
+}
+.highcharts-data-label-connector {
+    fill: none;
+}
+.highcharts-halo {
+    fill-opacity: 0.25;
+    stroke-width: 0;
+}
+.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
+.highcharts-markers .highcharts-point-select {
+    fill: $neutral-color-20;
+    stroke: $neutral-color-100;
+}
+.highcharts-column-series rect.highcharts-point {
+    // rect to prevent stroke on 3D columns
+    stroke: $background-color;
+}
+.highcharts-column-series .highcharts-point {
+    transition: fill-opacity 250ms;
+}
+.highcharts-column-series .highcharts-point-hover {
+    fill-opacity: 0.75;
+    transition: fill-opacity 50ms;
+}
+.highcharts-pie-series .highcharts-point {
+    stroke-linejoin: round;
+    stroke: $background-color;
+}
+.highcharts-pie-series .highcharts-point-hover {
+    fill-opacity: 0.75;
+    transition: fill-opacity 50ms;
+}
+.highcharts-funnel-series .highcharts-point {
+    stroke-linejoin: round;
+    stroke: $background-color;
+}
+.highcharts-funnel-series .highcharts-point-hover {
+    fill-opacity: 0.75;
+    transition: fill-opacity 50ms;
+}
+.highcharts-funnel-series .highcharts-point-select {
+    fill: inherit;
+    stroke: inherit;
+}
+.highcharts-pyramid-series .highcharts-point {
+    stroke-linejoin: round;
+    stroke: $background-color;
+}
+.highcharts-pyramid-series .highcharts-point-hover {
+    fill-opacity: 0.75;
+    transition: fill-opacity 50ms;
+}
+.highcharts-pyramid-series .highcharts-point-select {
+    fill: inherit;
+    stroke: inherit;
+}
+.highcharts-solidgauge-series .highcharts-point {
+    stroke-width: 0;
+}
+.highcharts-treemap-series .highcharts-point {
+    stroke-width: 1px;
+    stroke: $neutral-color-10;
+    transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
+}
+.highcharts-treemap-series .highcharts-point-hover {
+    stroke: $neutral-color-40;
+    transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
+}
+
+.highcharts-treemap-series .highcharts-above-level {
+    display: none;
+}
+.highcharts-treemap-series .highcharts-internal-node {
+    fill: none;
+}
+.highcharts-treemap-series .highcharts-internal-node-interactive {
+    fill-opacity: 0.15;
+    cursor: pointer;
+}
+.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
+    fill-opacity: 0.75;
+}
+
+.highcharts-vector-series .highcharts-point {
+    fill: none;
+    stroke-width: 2px;
+}
+
+.highcharts-windbarb-series .highcharts-point {
+    fill: none;
+    stroke-width: 2px;
+ }
+
+/* Dumbbell/lollipop connector */
+.highcharts-lollipop-stem {
+	stroke: $neutral-color-100;
+}
+
+
+/* Legend */
+.highcharts-legend-box {
+    fill: none;
+    stroke-width: 0;
+}
+.highcharts-legend-item > text {
+    fill: $neutral-color-80;
+    font-weight: bold;
+    font-size: $legend-font-size;
+    cursor: pointer;
+    stroke-width: 0;
+}
+.highcharts-legend-item:hover text {
+    fill: $neutral-color-100;
+}
+.highcharts-legend-item-hidden * {
+    fill: $neutral-color-20 !important;
+    stroke: $neutral-color-20 !important;
+    transition: fill 250ms;
+}
+.highcharts-legend-nav-active {
+    fill: $highlight-color-100;
+    cursor: pointer;
+}
+.highcharts-legend-nav-inactive {
+    fill: $neutral-color-20;
+}
+circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive { /* tracker */
+    fill: rgba(192, 192, 192, 0.0001);
+}
+.highcharts-legend-title-box {
+    fill: none;
+    stroke-width: 0;
+}
+
+/* Bubble legend */
+.highcharts-bubble-legend-symbol {
+    stroke-width: 2;
+    fill-opacity: 0.5;
+}
+.highcharts-bubble-legend-connectors {
+    stroke-width: 1;
+}
+.highcharts-bubble-legend-labels {
+    fill: $neutral-color-80;
+}
+
+/* Loading */
+.highcharts-loading {
+    position: absolute;
+    background-color: $background-color;
+    opacity: 0.5;
+    text-align: center;
+    z-index: 10;
+    transition: opacity 250ms;
+}
+.highcharts-loading-hidden {
+    height: 0 !important;
+    opacity: 0;
+    overflow: hidden;
+    transition: opacity 250ms, height 250ms step-end;
+}
+.highcharts-loading-inner {
+    font-weight: bold;
+    position: relative;
+    top: 45%;
+}
+
+/* Plot bands and polar pane backgrounds */
+.highcharts-plot-band, .highcharts-pane {
+    fill: $neutral-color-100;
+    fill-opacity: 0.05;
+}
+.highcharts-plot-line {
+    fill: none;
+    stroke: $neutral-color-40;
+    stroke-width: 1px;
+}
+
+/* Highcharts More and modules */
+.highcharts-boxplot-box {
+    fill: $background-color;
+}
+.highcharts-boxplot-median {
+    stroke-width: 2px;
+}
+.highcharts-bubble-series .highcharts-point {
+    fill-opacity: 0.5;
+}
+.highcharts-errorbar-series .highcharts-point {
+    stroke: $neutral-color-100;
+}
+.highcharts-gauge-series .highcharts-data-label-box {
+    stroke: $neutral-color-20;
+    stroke-width: 1px;
+}
+.highcharts-gauge-series .highcharts-dial {
+    fill: $neutral-color-100;
+    stroke-width: 0;
+}
+.highcharts-polygon-series .highcharts-graph {
+    fill: inherit;
+    stroke-width: 0;
+}
+.highcharts-waterfall-series .highcharts-graph {
+    stroke: $neutral-color-80;
+    stroke-dasharray: 1, 3;
+}
+.highcharts-sankey-series .highcharts-point {
+    stroke-width: 0;
+}
+.highcharts-sankey-series .highcharts-link {
+    transition: fill 250ms, fill-opacity 250ms;
+    fill-opacity: 0.5;
+}
+.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
+    transition: fill 50ms, fill-opacity 50ms;
+    fill-opacity: 1;
+}
+.highcharts-venn-series .highcharts-point {
+    fill-opacity: 0.75;
+    stroke: $neutral-color-20;
+    transition: stroke 250ms, fill-opacity 250ms;
+}
+.highcharts-venn-series .highcharts-point-hover {
+    fill-opacity: 1;
+    stroke: $neutral-color-20;
+}
+
+/* Highstock */
+.highcharts-navigator-mask-outside {
+    fill-opacity: 0;
+}
+.highcharts-navigator-mask-inside {
+    fill: $highlight-color-60; /* navigator.maskFill option */
+    fill-opacity: 0.25;
+    cursor: ew-resize;
+}
+.highcharts-navigator-outline {
+    stroke: $neutral-color-20;
+    fill: none;
+}
+.highcharts-navigator-handle {
+    stroke: $neutral-color-20;
+    fill: $neutral-color-5;
+    cursor: ew-resize;
+}
+.highcharts-navigator-series {
+    fill: $navigator-series-fill;
+    stroke: $navigator-series-border;
+}
+.highcharts-navigator-series .highcharts-graph {
+    stroke-width: 1px;
+}
+.highcharts-navigator-series .highcharts-area {
+    fill-opacity: 0.05;
+}
+.highcharts-navigator-xaxis .highcharts-axis-line {
+    stroke-width: 0;
+}
+.highcharts-navigator-xaxis .highcharts-grid-line {
+    stroke-width: 1px;
+    stroke: $neutral-color-10;
+}
+.highcharts-navigator-xaxis.highcharts-axis-labels {
+    fill: $neutral-color-40;
+}
+.highcharts-navigator-yaxis .highcharts-grid-line {
+    stroke-width: 0;
+}
+.highcharts-scrollbar-thumb {
+    fill: $neutral-color-20;
+    stroke: $neutral-color-20;
+    stroke-width: 1px;
+}
+.highcharts-scrollbar-button {
+    fill: $neutral-color-10;
+    stroke: $neutral-color-20;
+    stroke-width: 1px;
+}
+.highcharts-scrollbar-arrow {
+    fill: $neutral-color-60;
+}
+.highcharts-scrollbar-rifles {
+    stroke: $neutral-color-60;
+    stroke-width: 1px;
+}
+.highcharts-scrollbar-track {
+    fill: $scrollbar-track-background;
+    stroke: $scrollbar-track-border;
+    stroke-width: 1px;
+}
+.highcharts-button {
+    fill: $highcharts-button-background;
+    stroke: $highcharts-button-border;
+    cursor: default;
+    stroke-width: 1px;
+    transition: fill 250ms;
+}
+.highcharts-button text {
+    fill: $highcharts-button-text;
+}
+.highcharts-button-hover {
+    transition: fill 0ms;
+    fill: $highcharts-button-hover-background;
+    stroke: $highcharts-button-hover-border;
+}
+.highcharts-button-hover text {
+    fill: $highcharts-button-hover-text;
+}
+.highcharts-button-pressed {
+    font-weight: bold;
+    fill: $highcharts-button-pressed-background;
+    stroke: $highcharts-button-pressed-border;
+}
+.highcharts-button-pressed text {
+    fill: $highcharts-button-pressed-text;
+    font-weight: bold;
+}
+.highcharts-button-disabled text {
+    fill: $highcharts-button-text;
+}
+.highcharts-range-selector-buttons .highcharts-button {
+    stroke-width: $range-selector-button-border;
+}
+.highcharts-range-label rect {
+    fill: none;
+}
+.highcharts-range-label text {
+    fill: $neutral-color-60;
+}
+.highcharts-range-input rect {
+    fill: none;
+}
+.highcharts-range-input text {
+    fill: $range-selector-input-text;
+}
+.highcharts-range-input {
+    stroke-width:1px;
+    stroke: $range-selector-input-border;
+}
+input.highcharts-range-selector {
+    position: absolute;
+    border: 0;
+    width: 1px; /* Chrome needs a pixel to see it */
+    height: 1px;
+    padding: 0;
+    text-align: center;
+    left: -9em; /* #4798 */
+}
+.highcharts-crosshair-label text {
+    fill: $background-color;
+    font-size: 1.1em;
+}
+.highcharts-crosshair-label .highcharts-label-box {
+    fill: inherit;
+}
+
+
+.highcharts-candlestick-series .highcharts-point {
+    stroke: $neutral-color-100;
+    stroke-width: 1px;
+}
+.highcharts-candlestick-series .highcharts-point-up {
+    fill: $background-color;
+}
+.highcharts-ohlc-series .highcharts-point-hover {
+    stroke-width: 3px;
+}
+.highcharts-flags-series .highcharts-point .highcharts-label-box {
+    stroke: $neutral-color-40;
+    fill: $background-color;
+    transition: fill 250ms;
+}
+.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
+    stroke: $neutral-color-100;
+    fill: $highlight-color-20;
+}
+.highcharts-flags-series .highcharts-point text {
+    fill: $neutral-color-100;
+    font-size: 0.9em;
+    font-weight: bold;
+}
+
+/* Highmaps */
+.highcharts-map-series .highcharts-point {
+    transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
+    stroke: $neutral-color-20;
+}
+.highcharts-map-series .highcharts-point-hover {
+    transition: fill 0ms, fill-opacity 0ms;
+    fill-opacity: 0.5;
+    stroke-width: 2px;
+}
+.highcharts-mapline-series .highcharts-point {
+    fill: none;
+}
+.highcharts-heatmap-series .highcharts-point {
+    stroke-width: 0;
+}
+.highcharts-map-navigation {
+    font-size: 1.3em;
+    font-weight: bold;
+    text-align: center;
+}
+.highcharts-coloraxis {
+    stroke-width: 0;
+}
+.highcharts-coloraxis-marker {
+    fill: $neutral-color-40;
+}
+.highcharts-null-point {
+    fill: $neutral-color-3;
+}
+
+/* 3d charts */
+.highcharts-3d-frame {
+    fill: transparent;
+}
+
+/* Exporting module */
+.highcharts-contextbutton {
+    fill: $context-button-background; /* needed to capture hover */
+    stroke: none;
+    stroke-linecap: round;
+}
+.highcharts-contextbutton:hover {
+    fill: $neutral-color-10;
+    stroke: $neutral-color-10;
+}
+.highcharts-button-symbol {
+    stroke: $neutral-color-60;
+    stroke-width: 3px;
+}
+.highcharts-menu {
+    border: 1px solid $neutral-color-40;
+    background: $background-color;
+    padding: 5px 0;
+    box-shadow: 3px 3px 10px #888;
+}
+.highcharts-menu-item {
+    padding: 0.5em 1em;
+    background: none;
+    color: $neutral-color-80;
+    cursor: pointer;
+    transition: background 250ms, color 250ms;
+}
+.highcharts-menu-item:hover {
+    background: $highlight-color-80;
+    color: $background-color;
+}
+
+/* Drilldown module */
+.highcharts-drilldown-point {
+    cursor: pointer;
+}
+.highcharts-drilldown-data-label text,
+text.highcharts-drilldown-data-label,
+.highcharts-drilldown-axis-label {
+    cursor: pointer;
+    fill: $highlight-color-100;
+    font-weight: bold;
+    text-decoration: underline;
+}
+
+/* No-data module */
+.highcharts-no-data text {
+    font-weight: bold;
+    font-size: 12px;
+    fill: $neutral-color-60;
+}
+
+/* Drag-panes module */
+.highcharts-axis-resizer {
+    cursor: ns-resize;
+    stroke: black;
+    stroke-width: 2px;
+}
+
+/* Bullet type series */
+.highcharts-bullet-target {
+    stroke-width: 0;
+}
+
+/* Lineargauge type series */
+.highcharts-lineargauge-target {
+	stroke-width: 1px;
+	stroke: $neutral-color-80;
+}
+.highcharts-lineargauge-target-line {
+	stroke-width: 1px;
+    stroke: $neutral-color-80;
+}
+
+/* Annotations module */
+.highcharts-annotation-label-box {
+    stroke-width: 1px;
+    stroke: $neutral-color-100;
+    fill: $neutral-color-100;
+    fill-opacity: 0.75;
+}
+.highcharts-annotation-label text {
+    fill: $neutral-color-10;
+}
+
+/* Gantt */
+.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
+    cursor: pointer;
+}
+.highcharts-point-connecting-path {
+    fill: none;
+}
+.highcharts-grid-axis .highcharts-tick {
+    stroke-width: 1px;
+}
+.highcharts-grid-axis .highcharts-axis-line {
+    stroke-width: 1px;
+}

+ 265 - 0
app/public/js/Highstock-8.0.0/code/css/stocktools/gui.css

@@ -0,0 +1,265 @@
+.chart:-webkit-full-screen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart:-moz-full-screen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart:-ms-fullscreen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart:fullscreen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart {
+  width: 100%;
+  float: left;
+  height: 400px;
+  position: relative;
+}
+
+.highcharts-draw-mode {
+  cursor: crosshair;
+}
+
+.highcharts-bindings-wrapper * {
+  box-sizing: content-box;
+}
+
+.highcharts-bindings-wrapper {
+  display: block;
+  width: 40px;
+  height: 100%;
+  position: absolute;
+  z-index: 10;
+}
+
+.highcharts-stocktools-popup {
+  width: 100%;
+}
+
+.highcharts-menu-wrapper {
+  float: left;
+  width: 40px;
+  height: calc(100% - 50px);
+  overflow: hidden;
+  position: absolute;
+  left: 0px;
+  top: 0px;
+  padding: 10px;
+}
+
+.highcharts-bindings-wrapper .highcharts-submenu-wrapper {
+  display: none;
+  position: absolute;
+  z-index: 10;
+  left: 0px;
+  top: 0px;
+  background: #fff;
+  width: 40px;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-wrapper {
+  text-align: center;
+  width: 40px;
+  position: absolute;
+  left: 10px;
+  bottom: 10px;
+  font-size: 1.5em;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div {
+  cursor: pointer;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-down {
+  background-size: cover;
+  /* Safari */
+  -webkit-transform: rotate(90deg);
+  /* Firefox */
+  -moz-transform: rotate(90deg);
+  /* IE */
+  -ms-transform: rotate(90deg);
+  /* Opera */
+  -o-transform: rotate(90deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+  transform: rotate(90deg);
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-up {
+  background-size: cover;
+  outline: none;
+  display: inline-block;
+  width: 25px;
+  cursor: pointer;
+  -webkit-user-select: none;
+  /* Chrome/Safari */
+  -moz-user-select: none;
+  /* Firefox */
+  -ms-user-select: none;
+  /* IE10+ */
+  /* Rules below not implemented in browsers yet */
+  -o-user-select: none;
+  user-select: none;
+  /* Safari */
+  -webkit-transform: rotate(-90deg);
+  /* Firefox */
+  -moz-transform: rotate(-90deg);
+  /* IE */
+  -ms-transform: rotate(-90deg);
+  /* Opera */
+  -o-transform: rotate(-90deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+  transform: rotate(-90deg);
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-right {
+  background-repeat: no-repeat;
+  background-position: right bottom;
+  background-size: contain;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-left.highcharts-arrow-right {
+  /* Safari */
+  -webkit-transform: rotate(0deg);
+  /* Firefox */
+  -moz-transform: rotate(0deg);
+  /* IE */
+  -ms-transform: rotate(0deg);
+  /* Opera */
+  -o-transform: rotate(0deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+  transform: rotate(0deg);
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-left {
+  /* Safari */
+  -webkit-transform: rotate(180deg);
+  /* Firefox */
+  -moz-transform: rotate(180deg);
+  /* IE */
+  -ms-transform: rotate(180deg);
+  /* Opera */
+  -o-transform: rotate(180deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+  transform: rotate(180deg);
+}
+
+.highcharts-bindings-wrapper ul {
+  width: 40px;
+  /* 30px spacing for arrows to scroll */
+  margin: 0px;
+  padding: 0px;
+  float: left;
+  transition: margin 250ms;
+}
+
+.highcharts-bindings-wrapper > ul {
+  width: 40px;
+  position: relative;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li {
+  list-style: none;
+  margin-bottom: 3px;
+  padding: 0px;
+  clear: both;
+  width: 100%;
+  height: 40px;
+  cursor: pointer;
+  position: relative;
+  background-color: #f7f7f7;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn {
+  cursor: default;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn > .highcharts-menu-item-btn {
+  opacity: 0.5;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn.highcharts-active {
+  background: #f7f7f7;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn .highcharts-menu-item-btn:hover {
+  background-color: transparent;
+}
+
+.highcharts-bindings-wrapper li > span.highcharts-menu-item-btn {
+  display: block;
+  float: left;
+  width: 100%;
+  height: 100%;
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+}
+
+.highcharts-submenu-wrapper li > span.highcharts-menu-item-btn {
+  width: 40px;
+}
+
+.highcharts-bindings-wrapper li > span.highcharts-submenu-item-arrow {
+  float: left;
+  width: 10px;
+  height: 100%;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0px;
+  right: 0px;
+}
+
+.highcharts-bindings-wrapper li.highcharts-separator {
+  height: 15px;
+  background-color: transparent;
+  width: 36px;
+  pointer-events: none;
+}
+
+.highcharts-bindings-wrapper li.highcharts-separator > span.highcharts-menu-item-btn {
+  width: 100%;
+}
+
+.highcharts-bindings-wrapper li.highcharts-active > span.highcharts-menu-item-btn,
+.highcharts-bindings-wrapper li > span.highcharts-menu-item-btn:hover,
+.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div:hover,
+.highcharts-bindings-wrapper li.highcharts-active,
+.highcharts-toggle-toolbar:hover {
+  background-color: #e6ebf5;
+  transition: background-color 100ms;
+}
+
+.highcharts-toggle-toolbar {
+  position: absolute;
+  cursor: pointer;
+  width: 10px;
+  height: 10px;
+  background-color: #f7f7f7;
+  background-size: cover;
+}
+
+.highcharts-hide {
+  display: none;
+}
+
+.highcharts-bindings-wrapper li:hover, .highcharts-submenu-item-arrow:hover {
+  background-color: #e6ebf5;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-down, .highcharts-bindings-wrapper .highcharts-arrow-up {
+  width: 50%;
+  height: 20px;
+  float: left;
+}

+ 265 - 0
app/public/js/Highstock-8.0.0/code/css/stocktools/gui.scss

@@ -0,0 +1,265 @@
+// Colors for buttons.
+$button-background-color: #f7f7f7;
+$button-hover-color: #e6ebf5;
+
+.chart:-webkit-full-screen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart:-moz-full-screen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart:-ms-fullscreen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart:fullscreen {
+  width: 100%;
+  height: 100%;
+}
+
+.chart {
+  width: 100%;
+  float: left;
+  height: 400px;
+  position: relative;
+}
+
+.highcharts-draw-mode { cursor: crosshair; }
+
+.highcharts-bindings-wrapper * {
+  box-sizing: content-box;
+}
+
+.highcharts-bindings-wrapper {
+  display: block;
+  width: 40px;
+  height: 100%;
+  position: absolute;
+  z-index: 10;
+}
+
+.highcharts-stocktools-popup {
+  width: 100%;
+}
+
+.highcharts-menu-wrapper {
+  float: left;
+  width: 40px;
+  height: calc(100% - 50px);
+  overflow: hidden;
+  position: absolute;
+  left: 0px;
+  top: 0px;
+  padding: 10px;
+}
+
+.highcharts-bindings-wrapper .highcharts-submenu-wrapper {
+  display: none;
+  position: absolute;
+  z-index: 10;
+  left: 0px;
+  top: 0px;
+  background: #fff;
+  width: 40px;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-wrapper {
+  text-align: center;
+  width: 40px;
+  position: absolute;
+  left: 10px;
+  bottom: 10px;
+  font-size: 1.5em;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div {
+  cursor: pointer;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-down {
+  background-size: cover;
+  /* Safari */
+  -webkit-transform: rotate(90deg);
+  /* Firefox */
+  -moz-transform: rotate(90deg);
+  /* IE */
+  -ms-transform: rotate(90deg);
+  /* Opera */
+  -o-transform: rotate(90deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+  transform: rotate(90deg);
+}
+.highcharts-bindings-wrapper .highcharts-arrow-up {
+  background-size: cover;
+  outline: none;
+  display: inline-block;
+  width: 25px;
+  cursor: pointer;
+  -webkit-user-select: none;
+  /* Chrome/Safari */
+  -moz-user-select: none;
+  /* Firefox */
+  -ms-user-select: none;
+  /* IE10+ */
+  /* Rules below not implemented in browsers yet */
+  -o-user-select: none;
+  user-select: none;
+  /* Safari */
+  -webkit-transform: rotate(-90deg);
+  /* Firefox */
+  -moz-transform: rotate(-90deg);
+  /* IE */
+  -ms-transform: rotate(-90deg);
+  /* Opera */
+  -o-transform: rotate(-90deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+  transform: rotate(-90deg);
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-right {
+  background-repeat: no-repeat;
+  background-position: right bottom;
+  background-size: contain;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-left.highcharts-arrow-right {
+   /* Safari */
+  -webkit-transform: rotate(0deg);
+  /* Firefox */
+  -moz-transform: rotate(0deg);
+  /* IE */
+  -ms-transform: rotate(0deg);
+  /* Opera */
+  -o-transform: rotate(0deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+  transform: rotate(0deg);
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-left {
+  /* Safari */
+  -webkit-transform: rotate(180deg);
+  /* Firefox */
+  -moz-transform: rotate(180deg);
+  /* IE */
+  -ms-transform: rotate(180deg);
+  /* Opera */
+  -o-transform: rotate(180deg);
+  /* Internet Explorer */
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+  transform: rotate(180deg);
+}
+
+.highcharts-bindings-wrapper ul {
+  width: 40px;
+  /* 30px spacing for arrows to scroll */
+  margin: 0px;
+  padding: 0px;
+  float: left;
+  transition: margin 250ms;
+}
+
+.highcharts-bindings-wrapper>ul {
+  width: 40px;
+  position: relative;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li {
+  list-style: none;
+  margin-bottom: 3px;
+  padding: 0px;
+  clear: both;
+  width: 100%;
+  height: 40px;
+  cursor: pointer;
+  position: relative;
+  background-color: $button-background-color;
+}
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn {
+  cursor: default;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn > .highcharts-menu-item-btn {
+  opacity: 0.5;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn.highcharts-active {
+  background: $button-background-color;
+}
+
+.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn .highcharts-menu-item-btn:hover {
+  background-color: transparent;
+}
+
+.highcharts-bindings-wrapper li>span.highcharts-menu-item-btn {
+  display: block;
+  float: left;
+  width: 100%;
+  height: 100%;
+  background-repeat: no-repeat;
+  background-position: 50% 50%;
+}
+
+.highcharts-submenu-wrapper li>span.highcharts-menu-item-btn {
+  width: 40px;
+}
+
+.highcharts-bindings-wrapper li>span.highcharts-submenu-item-arrow {
+  float: left;
+  width: 10px;
+  height: 100%;
+  cursor: pointer;
+  position: absolute;
+  bottom: 0px;
+  right: 0px;
+}
+
+.highcharts-bindings-wrapper li.highcharts-separator {
+  height: 15px;
+  background-color: transparent;
+  width: 36px;
+  pointer-events: none;
+}
+
+.highcharts-bindings-wrapper li.highcharts-separator>span.highcharts-menu-item-btn {
+  width: 100%;
+}
+
+.highcharts-bindings-wrapper li.highcharts-active>span.highcharts-menu-item-btn,
+.highcharts-bindings-wrapper li>span.highcharts-menu-item-btn:hover,
+.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div:hover,
+.highcharts-bindings-wrapper li.highcharts-active,
+.highcharts-toggle-toolbar:hover {
+  background-color: $button-hover-color;
+  transition: background-color 100ms;
+}
+
+.highcharts-toggle-toolbar {
+  position: absolute;
+  cursor: pointer;
+  width: 10px;
+  height: 10px;
+  background-color: $button-background-color;
+  background-size: cover;
+}
+
+.highcharts-hide {
+  display: none;
+}
+
+.highcharts-bindings-wrapper li:hover, .highcharts-submenu-item-arrow:hover {
+  background-color: $button-hover-color;
+}
+
+.highcharts-bindings-wrapper .highcharts-arrow-down, .highcharts-bindings-wrapper .highcharts-arrow-up {
+  width: 50%;
+  height: 20px;
+  float: left;
+}

+ 987 - 0
app/public/js/Highstock-8.0.0/code/css/themes/dark-unica.css

@@ -0,0 +1,987 @@
+@import 'https://fonts.googleapis.com/css?family=Unica+One';
+.highcharts-title, .highcharts-subtitle {
+  text-transform: uppercase;
+}
+
+.highcharts-tooltip text {
+  fill: #F0F0F0;
+}
+
+.highcharts-range-selector-buttons text {
+  fill: silver;
+}
+
+.highcharts-yaxis-grid {
+  stroke-width: 1px;
+}
+
+.highcharts-axis-labels, .highcharts-axis-title {
+  fill: #E0E0E3;
+}
+
+.highcharts-navigator .highcharts-navigator-handle {
+  fill: #666;
+  stroke: #aaa;
+}
+
+.highcharts-navigator .highcharts-navigator-outline {
+  stroke: #CCC;
+}
+
+.highcharts-navigator .highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke: #505053;
+}
+
+.highcharts-scrollbar .highcharts-scrollbar-rifles {
+  stroke: #fff;
+}
+
+.highcharts-scrollbar .highcharts-scrollbar-button {
+  stroke: #606063;
+  fill: #606063;
+}
+
+.highcharts-scrollbar .highcharts-scrollbar-arrow {
+  fill: #CCC;
+}
+
+.highcharts-scrollbar .highcharts-scrollbar-thumb {
+  fill: #808083;
+  stroke: #808083;
+}
+
+.highcharts-contextbutton .highcharts-button-symbol {
+  stroke: #DDDDDD;
+}
+
+/**
+ * @license Highcharts
+ *
+ * (c) 2009-2016 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+.highcharts-container {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+  text-align: left;
+  line-height: normal;
+  z-index: 0;
+  /* #1072 */
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+  font-family: "Unica One", Arial, Helvetica, sans-serif;
+  font-size: 12px;
+}
+
+.highcharts-root {
+  display: block;
+}
+
+.highcharts-root text {
+  stroke-width: 0;
+}
+
+.highcharts-strong {
+  font-weight: bold;
+}
+
+.highcharts-emphasized {
+  font-style: italic;
+}
+
+.highcharts-anchor {
+  cursor: pointer;
+}
+
+.highcharts-background {
+  fill: #2a2a2b;
+}
+
+.highcharts-plot-border, .highcharts-plot-background {
+  fill: none;
+}
+
+.highcharts-label-box {
+  fill: none;
+}
+
+.highcharts-button-box {
+  fill: inherit;
+}
+
+.highcharts-tracker-line {
+  stroke-linejoin: round;
+  stroke: rgba(192, 192, 192, 0.0001);
+  stroke-width: 22;
+  fill: none;
+}
+
+.highcharts-tracker-area {
+  fill: rgba(192, 192, 192, 0.0001);
+  stroke-width: 0;
+}
+
+/* Titles */
+.highcharts-title {
+  fill: #E0E0E3;
+  font-size: 20px;
+}
+
+.highcharts-subtitle {
+  fill: #E0E0E3;
+}
+
+/* Axes */
+.highcharts-axis-line {
+  fill: none;
+  stroke: #707073;
+}
+
+.highcharts-yaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-axis-title {
+  fill: #E0E0E3;
+}
+
+.highcharts-axis-labels {
+  fill: #E0E0E3;
+  cursor: default;
+  font-size: 0.9em;
+}
+
+.highcharts-grid-line {
+  fill: none;
+  stroke: #707073;
+}
+
+.highcharts-xaxis-grid .highcharts-grid-line {
+  stroke-width: 0px;
+}
+
+.highcharts-tick {
+  stroke: #707073;
+}
+
+.highcharts-yaxis .highcharts-tick {
+  stroke-width: 0;
+}
+
+.highcharts-minor-grid-line {
+  stroke: #505053;
+}
+
+.highcharts-crosshair-thin {
+  stroke-width: 1px;
+  stroke: #606063;
+}
+
+.highcharts-crosshair-category {
+  stroke: #707073;
+  stroke-opacity: 0.25;
+}
+
+/* Credits */
+.highcharts-credits {
+  cursor: pointer;
+  fill: #666;
+  font-size: 0.7em;
+  transition: fill 250ms, font-size 250ms;
+}
+
+.highcharts-credits:hover {
+  fill: black;
+  font-size: 1em;
+}
+
+/* Tooltip */
+.highcharts-tooltip {
+  cursor: default;
+  pointer-events: none;
+  white-space: nowrap;
+  transition: stroke 150ms;
+}
+
+.highcharts-tooltip text {
+  fill: #E0E0E3;
+}
+
+.highcharts-tooltip .highcharts-header {
+  font-size: 0.85em;
+}
+
+.highcharts-tooltip-box {
+  stroke-width: 1px;
+  fill: rgba(0, 0, 0, 0.85);
+  fill-opacity: 0.85;
+}
+
+.highcharts-tooltip-box .highcharts-label-box {
+  fill: rgba(0, 0, 0, 0.85);
+  fill-opacity: 0.85;
+}
+
+div.highcharts-tooltip {
+  filter: none;
+}
+
+.highcharts-selection-marker {
+  fill: #335cad;
+  fill-opacity: 0.25;
+}
+
+.highcharts-graph {
+  fill: none;
+  stroke-width: 2px;
+  stroke-linecap: round;
+  stroke-linejoin: round;
+}
+
+.highcharts-state-hover .highcharts-graph {
+  stroke-width: 3;
+}
+
+.highcharts-point-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-series-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-state-hover path {
+  transition: stroke-width 50ms;
+  /* quick in */
+}
+
+.highcharts-state-normal path {
+  transition: stroke-width 250ms;
+  /* slow out */
+}
+
+/* Legend hover affects points and series */
+g.highcharts-series,
+.highcharts-point,
+.highcharts-markers,
+.highcharts-data-labels {
+  transition: opacity 250ms;
+}
+
+.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
+.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
+.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
+.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
+  opacity: 0.2;
+}
+
+/* Series options */
+/* Default colors */
+.highcharts-color-0 {
+  fill: #2b908f;
+  stroke: #2b908f;
+}
+
+.highcharts-color-1 {
+  fill: #90ee7e;
+  stroke: #90ee7e;
+}
+
+.highcharts-color-2 {
+  fill: #f45b5b;
+  stroke: #f45b5b;
+}
+
+.highcharts-color-3 {
+  fill: #7798BF;
+  stroke: #7798BF;
+}
+
+.highcharts-color-4 {
+  fill: #aaeeee;
+  stroke: #aaeeee;
+}
+
+.highcharts-color-5 {
+  fill: #ff0066;
+  stroke: #ff0066;
+}
+
+.highcharts-color-6 {
+  fill: #eeaaee;
+  stroke: #eeaaee;
+}
+
+.highcharts-color-7 {
+  fill: #55BF3B;
+  stroke: #55BF3B;
+}
+
+.highcharts-color-8 {
+  fill: #DF5353;
+  stroke: #DF5353;
+}
+
+.highcharts-color-9 {
+  fill: #7798BF;
+  stroke: #7798BF;
+}
+
+.highcharts-color-10 {
+  fill: #aaeeee;
+  stroke: #aaeeee;
+}
+
+.highcharts-area {
+  fill-opacity: 0.75;
+  stroke-width: 0;
+}
+
+.highcharts-markers {
+  stroke-width: 1px;
+  stroke: #2a2a2b;
+}
+
+.highcharts-point {
+  stroke-width: 1px;
+}
+
+.highcharts-dense-data .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-data-label {
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+.highcharts-data-label-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-data-label text, text.highcharts-data-label {
+  fill: #B0B0B3;
+}
+
+.highcharts-data-label-connector {
+  fill: none;
+}
+
+.highcharts-halo {
+  fill-opacity: 0.25;
+  stroke-width: 0;
+}
+
+.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
+.highcharts-markers .highcharts-point-select {
+  fill: #606063;
+  stroke: #fff;
+}
+
+.highcharts-column-series rect.highcharts-point {
+  stroke: #2a2a2b;
+}
+
+.highcharts-column-series .highcharts-point {
+  transition: fill-opacity 250ms;
+}
+
+.highcharts-column-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pie-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #2a2a2b;
+}
+
+.highcharts-pie-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #2a2a2b;
+}
+
+.highcharts-funnel-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-pyramid-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #2a2a2b;
+}
+
+.highcharts-pyramid-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pyramid-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-solidgauge-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-treemap-series .highcharts-point {
+  stroke-width: 1px;
+  stroke: #707073;
+  transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
+}
+
+.highcharts-treemap-series .highcharts-point-hover {
+  stroke: #666;
+  transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
+}
+
+.highcharts-treemap-series .highcharts-above-level {
+  display: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node {
+  fill: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive {
+  fill-opacity: 0.15;
+  cursor: pointer;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
+  fill-opacity: 0.75;
+}
+
+.highcharts-vector-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+.highcharts-windbarb-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+/* Dumbbell/lollipop connector */
+.highcharts-lollipop-stem {
+  stroke: #fff;
+}
+
+/* Legend */
+.highcharts-legend-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item > text {
+  fill: #E0E0E3;
+  font-weight: bold;
+  font-size: 1em;
+  cursor: pointer;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item:hover text {
+  fill: #fff;
+}
+
+.highcharts-legend-item-hidden * {
+  fill: #606063 !important;
+  stroke: #606063 !important;
+  transition: fill 250ms;
+}
+
+.highcharts-legend-nav-active {
+  fill: #F0F0F3;
+  cursor: pointer;
+}
+
+.highcharts-legend-nav-inactive {
+  fill: #606063;
+}
+
+circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive {
+  /* tracker */
+  fill: rgba(192, 192, 192, 0.0001);
+}
+
+.highcharts-legend-title-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+/* Bubble legend */
+.highcharts-bubble-legend-symbol {
+  stroke-width: 2;
+  fill-opacity: 0.5;
+}
+
+.highcharts-bubble-legend-connectors {
+  stroke-width: 1;
+}
+
+.highcharts-bubble-legend-labels {
+  fill: #E0E0E3;
+}
+
+/* Loading */
+.highcharts-loading {
+  position: absolute;
+  background-color: #2a2a2b;
+  opacity: 0.5;
+  text-align: center;
+  z-index: 10;
+  transition: opacity 250ms;
+}
+
+.highcharts-loading-hidden {
+  height: 0 !important;
+  opacity: 0;
+  overflow: hidden;
+  transition: opacity 250ms, height 250ms step-end;
+}
+
+.highcharts-loading-inner {
+  font-weight: bold;
+  position: relative;
+  top: 45%;
+}
+
+/* Plot bands and polar pane backgrounds */
+.highcharts-plot-band, .highcharts-pane {
+  fill: #fff;
+  fill-opacity: 0.05;
+}
+
+.highcharts-plot-line {
+  fill: none;
+  stroke: #666;
+  stroke-width: 1px;
+}
+
+/* Highcharts More and modules */
+.highcharts-boxplot-box {
+  fill: #2a2a2b;
+}
+
+.highcharts-boxplot-median {
+  stroke-width: 2px;
+}
+
+.highcharts-bubble-series .highcharts-point {
+  fill-opacity: 0.5;
+}
+
+.highcharts-errorbar-series .highcharts-point {
+  stroke: #fff;
+}
+
+.highcharts-gauge-series .highcharts-data-label-box {
+  stroke: #606063;
+  stroke-width: 1px;
+}
+
+.highcharts-gauge-series .highcharts-dial {
+  fill: #fff;
+  stroke-width: 0;
+}
+
+.highcharts-polygon-series .highcharts-graph {
+  fill: inherit;
+  stroke-width: 0;
+}
+
+.highcharts-waterfall-series .highcharts-graph {
+  stroke: #E0E0E3;
+  stroke-dasharray: 1, 3;
+}
+
+.highcharts-sankey-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-sankey-series .highcharts-link {
+  transition: fill 250ms, fill-opacity 250ms;
+  fill-opacity: 0.5;
+}
+
+.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
+  transition: fill 50ms, fill-opacity 50ms;
+  fill-opacity: 1;
+}
+
+.highcharts-venn-series .highcharts-point {
+  fill-opacity: 0.75;
+  stroke: #606063;
+  transition: stroke 250ms, fill-opacity 250ms;
+}
+
+.highcharts-venn-series .highcharts-point-hover {
+  fill-opacity: 1;
+  stroke: #606063;
+}
+
+/* Highstock */
+.highcharts-navigator-mask-outside {
+  fill-opacity: 0;
+}
+
+.highcharts-navigator-mask-inside {
+  fill: rgba(255, 255, 255, 0.1);
+  /* navigator.maskFill option */
+  fill-opacity: 0.25;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-outline {
+  stroke: #606063;
+  fill: none;
+}
+
+.highcharts-navigator-handle {
+  stroke: #606063;
+  fill: #505053;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-series {
+  fill: #7798BF;
+  stroke: #A6C7ED;
+}
+
+.highcharts-navigator-series .highcharts-graph {
+  stroke-width: 1px;
+}
+
+.highcharts-navigator-series .highcharts-area {
+  fill-opacity: 0.05;
+}
+
+.highcharts-navigator-xaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke-width: 1px;
+  stroke: #707073;
+}
+
+.highcharts-navigator-xaxis.highcharts-axis-labels {
+  fill: #666;
+}
+
+.highcharts-navigator-yaxis .highcharts-grid-line {
+  stroke-width: 0;
+}
+
+.highcharts-scrollbar-thumb {
+  fill: #606063;
+  stroke: #606063;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-button {
+  fill: #707073;
+  stroke: #606063;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-arrow {
+  fill: #E0E0E3;
+}
+
+.highcharts-scrollbar-rifles {
+  stroke: #E0E0E3;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-track {
+  fill: #404043;
+  stroke: #404043;
+  stroke-width: 1px;
+}
+
+.highcharts-button {
+  fill: #505053;
+  stroke: #606063;
+  cursor: default;
+  stroke-width: 1px;
+  transition: fill 250ms;
+}
+
+.highcharts-button text {
+  fill: #ccc;
+}
+
+.highcharts-button-hover {
+  transition: fill 0ms;
+  fill: #707073;
+  stroke: #606063;
+}
+
+.highcharts-button-hover text {
+  fill: #fff;
+}
+
+.highcharts-button-pressed {
+  font-weight: bold;
+  fill: #000003;
+  stroke: #606063;
+}
+
+.highcharts-button-pressed text {
+  fill: #fff;
+  font-weight: bold;
+}
+
+.highcharts-button-disabled text {
+  fill: #ccc;
+}
+
+.highcharts-range-selector-buttons .highcharts-button {
+  stroke-width: 0px;
+}
+
+.highcharts-range-label rect {
+  fill: none;
+}
+
+.highcharts-range-label text {
+  fill: #E0E0E3;
+}
+
+.highcharts-range-input rect {
+  fill: none;
+}
+
+.highcharts-range-input text {
+  fill: silver;
+}
+
+.highcharts-range-input {
+  stroke-width: 1px;
+  stroke: #505053;
+}
+
+input.highcharts-range-selector {
+  position: absolute;
+  border: 0;
+  width: 1px;
+  /* Chrome needs a pixel to see it */
+  height: 1px;
+  padding: 0;
+  text-align: center;
+  left: -9em;
+  /* #4798 */
+}
+
+.highcharts-crosshair-label text {
+  fill: #2a2a2b;
+  font-size: 1.1em;
+}
+
+.highcharts-crosshair-label .highcharts-label-box {
+  fill: inherit;
+}
+
+.highcharts-candlestick-series .highcharts-point {
+  stroke: #fff;
+  stroke-width: 1px;
+}
+
+.highcharts-candlestick-series .highcharts-point-up {
+  fill: #2a2a2b;
+}
+
+.highcharts-ohlc-series .highcharts-point-hover {
+  stroke-width: 3px;
+}
+
+.highcharts-flags-series .highcharts-point .highcharts-label-box {
+  stroke: #666;
+  fill: #2a2a2b;
+  transition: fill 250ms;
+}
+
+.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
+  stroke: #fff;
+  fill: #707073;
+}
+
+.highcharts-flags-series .highcharts-point text {
+  fill: #fff;
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+/* Highmaps */
+.highcharts-map-series .highcharts-point {
+  transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
+  stroke: #606063;
+}
+
+.highcharts-map-series .highcharts-point-hover {
+  transition: fill 0ms, fill-opacity 0ms;
+  fill-opacity: 0.5;
+  stroke-width: 2px;
+}
+
+.highcharts-mapline-series .highcharts-point {
+  fill: none;
+}
+
+.highcharts-heatmap-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-map-navigation {
+  font-size: 1.3em;
+  font-weight: bold;
+  text-align: center;
+}
+
+.highcharts-coloraxis {
+  stroke-width: 0;
+}
+
+.highcharts-coloraxis-marker {
+  fill: #666;
+}
+
+.highcharts-null-point {
+  fill: #f7f7f7;
+}
+
+/* 3d charts */
+.highcharts-3d-frame {
+  fill: transparent;
+}
+
+/* Exporting module */
+.highcharts-contextbutton {
+  fill: #505053;
+  /* needed to capture hover */
+  stroke: none;
+  stroke-linecap: round;
+}
+
+.highcharts-contextbutton:hover {
+  fill: #707073;
+  stroke: #707073;
+}
+
+.highcharts-button-symbol {
+  stroke: #E0E0E3;
+  stroke-width: 3px;
+}
+
+.highcharts-menu {
+  border: 1px solid #666;
+  background: #2a2a2b;
+  padding: 5px 0;
+  box-shadow: 3px 3px 10px #888;
+}
+
+.highcharts-menu-item {
+  padding: 0.5em 1em;
+  background: none;
+  color: #E0E0E3;
+  cursor: pointer;
+  transition: background 250ms, color 250ms;
+}
+
+.highcharts-menu-item:hover {
+  background: #335cad;
+  color: #2a2a2b;
+}
+
+/* Drilldown module */
+.highcharts-drilldown-point {
+  cursor: pointer;
+}
+
+.highcharts-drilldown-data-label text,
+text.highcharts-drilldown-data-label,
+.highcharts-drilldown-axis-label {
+  cursor: pointer;
+  fill: #F0F0F3;
+  font-weight: bold;
+  text-decoration: underline;
+}
+
+/* No-data module */
+.highcharts-no-data text {
+  font-weight: bold;
+  font-size: 12px;
+  fill: #E0E0E3;
+}
+
+/* Drag-panes module */
+.highcharts-axis-resizer {
+  cursor: ns-resize;
+  stroke: black;
+  stroke-width: 2px;
+}
+
+/* Bullet type series */
+.highcharts-bullet-target {
+  stroke-width: 0;
+}
+
+/* Lineargauge type series */
+.highcharts-lineargauge-target {
+  stroke-width: 1px;
+  stroke: #E0E0E3;
+}
+
+.highcharts-lineargauge-target-line {
+  stroke-width: 1px;
+  stroke: #E0E0E3;
+}
+
+/* Annotations module */
+.highcharts-annotation-label-box {
+  stroke-width: 1px;
+  stroke: #fff;
+  fill: #fff;
+  fill-opacity: 0.75;
+}
+
+.highcharts-annotation-label text {
+  fill: #707073;
+}
+
+/* Gantt */
+.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
+  cursor: pointer;
+}
+
+.highcharts-point-connecting-path {
+  fill: none;
+}
+
+.highcharts-grid-axis .highcharts-tick {
+  stroke-width: 1px;
+}
+
+.highcharts-grid-axis .highcharts-axis-line {
+  stroke-width: 1px;
+}

+ 122 - 0
app/public/js/Highstock-8.0.0/code/css/themes/dark-unica.scss

@@ -0,0 +1,122 @@
+// Global font
+@import 'https://fonts.googleapis.com/css?family=Unica+One';
+
+// Chart background, point stroke for markers and columns etc
+$background-color: #2a2a2b;
+
+// Colors for data series and points.
+$colors: #2b908f #90ee7e #f45b5b #7798BF #aaeeee #ff0066 #eeaaee #55BF3B #DF5353 #7798BF #aaeeee;
+
+// Neutral colors
+$neutral-color-100: #fff;
+$neutral-color-80: #E0E0E3;
+$neutral-color-60: #E0E0E3;
+$neutral-color-40: #666;
+$neutral-color-20: #606063;
+$neutral-color-10: #707073;
+$neutral-color-5: #505053;
+
+// Colored, shades
+$highlight-color-100: #F0F0F3;
+$highlight-color-60: rgba(255,255,255,0.1);
+$highlight-color-20: $neutral-color-10;
+
+// Data-labels
+$data-label-color: #B0B0B3;
+
+// Fonts
+$font-family: 'Unica One', Arial, Helvetica, sans-serif;
+$title-font-size: 20px;
+
+// Tooltip
+$tooltip-background: rgba(0, 0, 0, 0.85);
+
+// Range-selector
+$range-selector-input-text: silver;
+$range-selector-input-border: $neutral-color-5;
+
+// Buttons
+$highcharts-button-background: $neutral-color-5;
+$highcharts-button-text:  #ccc;
+
+$highcharts-button-pressed-background: #000003;
+$highcharts-button-pressed-text:  $neutral-color-100;
+
+$highcharts-button-hover-background: $neutral-color-10;
+$highcharts-button-hover-text:  $neutral-color-100;
+
+$context-button-background: $neutral-color-5;
+
+// Navigator
+$navigator-series-fill: #7798BF;
+$navigator-series-border: #A6C7ED;
+
+// Navigator
+$scrollbar-track-background: #404043;
+$scrollbar-track-border: #404043;
+
+// Titles
+.highcharts-title, .highcharts-subtitle {
+	text-transform: uppercase;
+}
+
+// Tooltip
+.highcharts-tooltip text { 
+	fill: #F0F0F0
+}
+
+// Range-selector
+.highcharts-range-selector-buttons text {
+	fill: silver;
+}
+
+// Axes
+.highcharts-yaxis-grid  {
+	stroke-width: 1px;
+}
+
+.highcharts-axis-labels, .highcharts-axis-title {
+	fill: #E0E0E3;
+}
+
+// Navigator
+.highcharts-navigator .highcharts-navigator-handle {
+  fill: $neutral-color-40;
+  stroke: #aaa;
+}
+
+.highcharts-navigator .highcharts-navigator-outline {
+  stroke: #CCC;
+}
+
+.highcharts-navigator .highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke: $neutral-color-5;
+} 
+
+// Scrollbar
+.highcharts-scrollbar .highcharts-scrollbar-rifles {
+  stroke: $neutral-color-100;
+}
+
+.highcharts-scrollbar .highcharts-scrollbar-button { 
+  stroke: #606063;
+  fill: #606063;
+}
+
+.highcharts-scrollbar .highcharts-scrollbar-arrow { 
+  fill: #CCC;
+}
+
+.highcharts-scrollbar .highcharts-scrollbar-thumb { 
+   fill: #808083;
+   stroke: #808083;
+}
+
+// Navigation
+.highcharts-contextbutton .highcharts-button-symbol { 
+   stroke: #DDDDDD;
+}
+
+@import '../highcharts';
+
+

+ 940 - 0
app/public/js/Highstock-8.0.0/code/css/themes/grid-light.css

@@ -0,0 +1,940 @@
+@import 'https://fonts.googleapis.com/css?family=Dosis:400,600';
+.highcharts-title, .highcharts-subtitle, .highcharts-yaxis .highcharts-axis-title {
+  text-transform: uppercase;
+}
+
+.highcharts-title {
+  font-weight: bold;
+}
+
+/**
+ * @license Highcharts
+ *
+ * (c) 2009-2016 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+.highcharts-container {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+  text-align: left;
+  line-height: normal;
+  z-index: 0;
+  /* #1072 */
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+  font-family: "Dosis", Arial, Helvetica, sans-serif;
+  font-size: 12px;
+}
+
+.highcharts-root {
+  display: block;
+}
+
+.highcharts-root text {
+  stroke-width: 0;
+}
+
+.highcharts-strong {
+  font-weight: bold;
+}
+
+.highcharts-emphasized {
+  font-style: italic;
+}
+
+.highcharts-anchor {
+  cursor: pointer;
+}
+
+.highcharts-background {
+  fill: #ffffff;
+}
+
+.highcharts-plot-border, .highcharts-plot-background {
+  fill: none;
+}
+
+.highcharts-label-box {
+  fill: none;
+}
+
+.highcharts-button-box {
+  fill: inherit;
+}
+
+.highcharts-tracker-line {
+  stroke-linejoin: round;
+  stroke: rgba(192, 192, 192, 0.0001);
+  stroke-width: 22;
+  fill: none;
+}
+
+.highcharts-tracker-area {
+  fill: rgba(192, 192, 192, 0.0001);
+  stroke-width: 0;
+}
+
+/* Titles */
+.highcharts-title {
+  fill: #000;
+  font-size: 16px;
+}
+
+.highcharts-subtitle {
+  fill: #666666;
+}
+
+/* Axes */
+.highcharts-axis-line {
+  fill: none;
+  stroke: #ccd6eb;
+}
+
+.highcharts-yaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-axis-title {
+  fill: #666666;
+}
+
+.highcharts-axis-labels {
+  fill: #666666;
+  cursor: default;
+  font-size: 12px;
+}
+
+.highcharts-grid-line {
+  fill: none;
+  stroke: #e6e6e6;
+}
+
+.highcharts-xaxis-grid .highcharts-grid-line {
+  stroke-width: 1px;
+}
+
+.highcharts-tick {
+  stroke: #ccd6eb;
+}
+
+.highcharts-yaxis .highcharts-tick {
+  stroke-width: 0;
+}
+
+.highcharts-minor-grid-line {
+  stroke: #f2f2f2;
+}
+
+.highcharts-crosshair-thin {
+  stroke-width: 1px;
+  stroke: #cccccc;
+}
+
+.highcharts-crosshair-category {
+  stroke: #ccd6eb;
+  stroke-opacity: 0.25;
+}
+
+/* Credits */
+.highcharts-credits {
+  cursor: pointer;
+  fill: #999999;
+  font-size: 0.7em;
+  transition: fill 250ms, font-size 250ms;
+}
+
+.highcharts-credits:hover {
+  fill: black;
+  font-size: 1em;
+}
+
+/* Tooltip */
+.highcharts-tooltip {
+  cursor: default;
+  pointer-events: none;
+  white-space: nowrap;
+  transition: stroke 150ms;
+}
+
+.highcharts-tooltip text {
+  fill: #000;
+}
+
+.highcharts-tooltip .highcharts-header {
+  font-size: 0.85em;
+}
+
+.highcharts-tooltip-box {
+  stroke-width: 0px;
+  fill: rgba(219, 219, 216, 0.8);
+  fill-opacity: 0.85;
+}
+
+.highcharts-tooltip-box .highcharts-label-box {
+  fill: rgba(219, 219, 216, 0.8);
+  fill-opacity: 0.85;
+}
+
+div.highcharts-tooltip {
+  filter: none;
+}
+
+.highcharts-selection-marker {
+  fill: #335cad;
+  fill-opacity: 0.25;
+}
+
+.highcharts-graph {
+  fill: none;
+  stroke-width: 2px;
+  stroke-linecap: round;
+  stroke-linejoin: round;
+}
+
+.highcharts-state-hover .highcharts-graph {
+  stroke-width: 3;
+}
+
+.highcharts-point-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-series-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-state-hover path {
+  transition: stroke-width 50ms;
+  /* quick in */
+}
+
+.highcharts-state-normal path {
+  transition: stroke-width 250ms;
+  /* slow out */
+}
+
+/* Legend hover affects points and series */
+g.highcharts-series,
+.highcharts-point,
+.highcharts-markers,
+.highcharts-data-labels {
+  transition: opacity 250ms;
+}
+
+.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
+.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
+.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
+.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
+  opacity: 0.2;
+}
+
+/* Series options */
+/* Default colors */
+.highcharts-color-0 {
+  fill: #7cb5ec;
+  stroke: #7cb5ec;
+}
+
+.highcharts-color-1 {
+  fill: #f7a35c;
+  stroke: #f7a35c;
+}
+
+.highcharts-color-2 {
+  fill: #90ee7e;
+  stroke: #90ee7e;
+}
+
+.highcharts-color-3 {
+  fill: #7798BF;
+  stroke: #7798BF;
+}
+
+.highcharts-color-4 {
+  fill: #aaeeee;
+  stroke: #aaeeee;
+}
+
+.highcharts-color-5 {
+  fill: #ff0066;
+  stroke: #ff0066;
+}
+
+.highcharts-color-6 {
+  fill: #eeaaee;
+  stroke: #eeaaee;
+}
+
+.highcharts-color-7 {
+  fill: #55BF3B;
+  stroke: #55BF3B;
+}
+
+.highcharts-color-8 {
+  fill: #DF5353;
+  stroke: #DF5353;
+}
+
+.highcharts-color-9 {
+  fill: #7798BF;
+  stroke: #7798BF;
+}
+
+.highcharts-color-10 {
+  fill: #aaeeee;
+  stroke: #aaeeee;
+}
+
+.highcharts-area {
+  fill-opacity: 0.75;
+  stroke-width: 0;
+}
+
+.highcharts-markers {
+  stroke-width: 1px;
+  stroke: #ffffff;
+}
+
+.highcharts-point {
+  stroke-width: 1px;
+}
+
+.highcharts-dense-data .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-data-label {
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+.highcharts-data-label-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-data-label text, text.highcharts-data-label {
+  fill: #000;
+}
+
+.highcharts-data-label-connector {
+  fill: none;
+}
+
+.highcharts-halo {
+  fill-opacity: 0.25;
+  stroke-width: 0;
+}
+
+.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
+.highcharts-markers .highcharts-point-select {
+  fill: #cccccc;
+  stroke: #404048;
+}
+
+.highcharts-column-series rect.highcharts-point {
+  stroke: #ffffff;
+}
+
+.highcharts-column-series .highcharts-point {
+  transition: fill-opacity 250ms;
+}
+
+.highcharts-column-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pie-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #ffffff;
+}
+
+.highcharts-pie-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #ffffff;
+}
+
+.highcharts-funnel-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-pyramid-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: #ffffff;
+}
+
+.highcharts-pyramid-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pyramid-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-solidgauge-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-treemap-series .highcharts-point {
+  stroke-width: 1px;
+  stroke: #e6e6e6;
+  transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
+}
+
+.highcharts-treemap-series .highcharts-point-hover {
+  stroke: #999999;
+  transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
+}
+
+.highcharts-treemap-series .highcharts-above-level {
+  display: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node {
+  fill: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive {
+  fill-opacity: 0.15;
+  cursor: pointer;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
+  fill-opacity: 0.75;
+}
+
+.highcharts-vector-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+.highcharts-windbarb-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+/* Dumbbell/lollipop connector */
+.highcharts-lollipop-stem {
+  stroke: #404048;
+}
+
+/* Legend */
+.highcharts-legend-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item > text {
+  fill: #000;
+  font-weight: bold;
+  font-size: 13px;
+  cursor: pointer;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item:hover text {
+  fill: #404048;
+}
+
+.highcharts-legend-item-hidden * {
+  fill: #cccccc !important;
+  stroke: #cccccc !important;
+  transition: fill 250ms;
+}
+
+.highcharts-legend-nav-active {
+  fill: #003399;
+  cursor: pointer;
+}
+
+.highcharts-legend-nav-inactive {
+  fill: #cccccc;
+}
+
+circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive {
+  /* tracker */
+  fill: rgba(192, 192, 192, 0.0001);
+}
+
+.highcharts-legend-title-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+/* Bubble legend */
+.highcharts-bubble-legend-symbol {
+  stroke-width: 2;
+  fill-opacity: 0.5;
+}
+
+.highcharts-bubble-legend-connectors {
+  stroke-width: 1;
+}
+
+.highcharts-bubble-legend-labels {
+  fill: #000;
+}
+
+/* Loading */
+.highcharts-loading {
+  position: absolute;
+  background-color: #ffffff;
+  opacity: 0.5;
+  text-align: center;
+  z-index: 10;
+  transition: opacity 250ms;
+}
+
+.highcharts-loading-hidden {
+  height: 0 !important;
+  opacity: 0;
+  overflow: hidden;
+  transition: opacity 250ms, height 250ms step-end;
+}
+
+.highcharts-loading-inner {
+  font-weight: bold;
+  position: relative;
+  top: 45%;
+}
+
+/* Plot bands and polar pane backgrounds */
+.highcharts-plot-band, .highcharts-pane {
+  fill: #404048;
+  fill-opacity: 0.05;
+}
+
+.highcharts-plot-line {
+  fill: none;
+  stroke: #999999;
+  stroke-width: 1px;
+}
+
+/* Highcharts More and modules */
+.highcharts-boxplot-box {
+  fill: #ffffff;
+}
+
+.highcharts-boxplot-median {
+  stroke-width: 2px;
+}
+
+.highcharts-bubble-series .highcharts-point {
+  fill-opacity: 0.5;
+}
+
+.highcharts-errorbar-series .highcharts-point {
+  stroke: #404048;
+}
+
+.highcharts-gauge-series .highcharts-data-label-box {
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-gauge-series .highcharts-dial {
+  fill: #404048;
+  stroke-width: 0;
+}
+
+.highcharts-polygon-series .highcharts-graph {
+  fill: inherit;
+  stroke-width: 0;
+}
+
+.highcharts-waterfall-series .highcharts-graph {
+  stroke: #000;
+  stroke-dasharray: 1, 3;
+}
+
+.highcharts-sankey-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-sankey-series .highcharts-link {
+  transition: fill 250ms, fill-opacity 250ms;
+  fill-opacity: 0.5;
+}
+
+.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
+  transition: fill 50ms, fill-opacity 50ms;
+  fill-opacity: 1;
+}
+
+.highcharts-venn-series .highcharts-point {
+  fill-opacity: 0.75;
+  stroke: #cccccc;
+  transition: stroke 250ms, fill-opacity 250ms;
+}
+
+.highcharts-venn-series .highcharts-point-hover {
+  fill-opacity: 1;
+  stroke: #cccccc;
+}
+
+/* Highstock */
+.highcharts-navigator-mask-outside {
+  fill-opacity: 0;
+}
+
+.highcharts-navigator-mask-inside {
+  fill: #6685c2;
+  /* navigator.maskFill option */
+  fill-opacity: 0.25;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-outline {
+  stroke: #cccccc;
+  fill: none;
+}
+
+.highcharts-navigator-handle {
+  stroke: #cccccc;
+  fill: #f2f2f2;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-series {
+  fill: #335cad;
+  stroke: #335cad;
+}
+
+.highcharts-navigator-series .highcharts-graph {
+  stroke-width: 1px;
+}
+
+.highcharts-navigator-series .highcharts-area {
+  fill-opacity: 0.05;
+}
+
+.highcharts-navigator-xaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke-width: 1px;
+  stroke: #e6e6e6;
+}
+
+.highcharts-navigator-xaxis.highcharts-axis-labels {
+  fill: #999999;
+}
+
+.highcharts-navigator-yaxis .highcharts-grid-line {
+  stroke-width: 0;
+}
+
+.highcharts-scrollbar-thumb {
+  fill: #cccccc;
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-button {
+  fill: #e6e6e6;
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-arrow {
+  fill: #666666;
+}
+
+.highcharts-scrollbar-rifles {
+  stroke: #666666;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-track {
+  fill: #f2f2f2;
+  stroke: #f2f2f2;
+  stroke-width: 1px;
+}
+
+.highcharts-button {
+  fill: #f7f7f7;
+  stroke: #cccccc;
+  cursor: default;
+  stroke-width: 1px;
+  transition: fill 250ms;
+}
+
+.highcharts-button text {
+  fill: #000;
+}
+
+.highcharts-button-hover {
+  transition: fill 0ms;
+  fill: #e6e6e6;
+  stroke: #cccccc;
+}
+
+.highcharts-button-hover text {
+  fill: #000;
+}
+
+.highcharts-button-pressed {
+  font-weight: bold;
+  fill: #e6ebf5;
+  stroke: #cccccc;
+}
+
+.highcharts-button-pressed text {
+  fill: #000;
+  font-weight: bold;
+}
+
+.highcharts-button-disabled text {
+  fill: #000;
+}
+
+.highcharts-range-selector-buttons .highcharts-button {
+  stroke-width: 0px;
+}
+
+.highcharts-range-label rect {
+  fill: none;
+}
+
+.highcharts-range-label text {
+  fill: #666666;
+}
+
+.highcharts-range-input rect {
+  fill: none;
+}
+
+.highcharts-range-input text {
+  fill: #000;
+}
+
+.highcharts-range-input {
+  stroke-width: 1px;
+  stroke: #cccccc;
+}
+
+input.highcharts-range-selector {
+  position: absolute;
+  border: 0;
+  width: 1px;
+  /* Chrome needs a pixel to see it */
+  height: 1px;
+  padding: 0;
+  text-align: center;
+  left: -9em;
+  /* #4798 */
+}
+
+.highcharts-crosshair-label text {
+  fill: #ffffff;
+  font-size: 1.1em;
+}
+
+.highcharts-crosshair-label .highcharts-label-box {
+  fill: inherit;
+}
+
+.highcharts-candlestick-series .highcharts-point {
+  stroke: #404048;
+  stroke-width: 1px;
+}
+
+.highcharts-candlestick-series .highcharts-point-up {
+  fill: #ffffff;
+}
+
+.highcharts-ohlc-series .highcharts-point-hover {
+  stroke-width: 3px;
+}
+
+.highcharts-flags-series .highcharts-point .highcharts-label-box {
+  stroke: #999999;
+  fill: #ffffff;
+  transition: fill 250ms;
+}
+
+.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
+  stroke: #404048;
+  fill: #ccd6eb;
+}
+
+.highcharts-flags-series .highcharts-point text {
+  fill: #404048;
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+/* Highmaps */
+.highcharts-map-series .highcharts-point {
+  transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
+  stroke: #cccccc;
+}
+
+.highcharts-map-series .highcharts-point-hover {
+  transition: fill 0ms, fill-opacity 0ms;
+  fill-opacity: 0.5;
+  stroke-width: 2px;
+}
+
+.highcharts-mapline-series .highcharts-point {
+  fill: none;
+}
+
+.highcharts-heatmap-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-map-navigation {
+  font-size: 1.3em;
+  font-weight: bold;
+  text-align: center;
+}
+
+.highcharts-coloraxis {
+  stroke-width: 0;
+}
+
+.highcharts-coloraxis-marker {
+  fill: #999999;
+}
+
+.highcharts-null-point {
+  fill: #f7f7f7;
+}
+
+/* 3d charts */
+.highcharts-3d-frame {
+  fill: transparent;
+}
+
+/* Exporting module */
+.highcharts-contextbutton {
+  fill: #ffffff;
+  /* needed to capture hover */
+  stroke: none;
+  stroke-linecap: round;
+}
+
+.highcharts-contextbutton:hover {
+  fill: #e6e6e6;
+  stroke: #e6e6e6;
+}
+
+.highcharts-button-symbol {
+  stroke: #666666;
+  stroke-width: 3px;
+}
+
+.highcharts-menu {
+  border: 1px solid #999999;
+  background: #ffffff;
+  padding: 5px 0;
+  box-shadow: 3px 3px 10px #888;
+}
+
+.highcharts-menu-item {
+  padding: 0.5em 1em;
+  background: none;
+  color: #000;
+  cursor: pointer;
+  transition: background 250ms, color 250ms;
+}
+
+.highcharts-menu-item:hover {
+  background: #335cad;
+  color: #ffffff;
+}
+
+/* Drilldown module */
+.highcharts-drilldown-point {
+  cursor: pointer;
+}
+
+.highcharts-drilldown-data-label text,
+text.highcharts-drilldown-data-label,
+.highcharts-drilldown-axis-label {
+  cursor: pointer;
+  fill: #003399;
+  font-weight: bold;
+  text-decoration: underline;
+}
+
+/* No-data module */
+.highcharts-no-data text {
+  font-weight: bold;
+  font-size: 12px;
+  fill: #666666;
+}
+
+/* Drag-panes module */
+.highcharts-axis-resizer {
+  cursor: ns-resize;
+  stroke: black;
+  stroke-width: 2px;
+}
+
+/* Bullet type series */
+.highcharts-bullet-target {
+  stroke-width: 0;
+}
+
+/* Lineargauge type series */
+.highcharts-lineargauge-target {
+  stroke-width: 1px;
+  stroke: #000;
+}
+
+.highcharts-lineargauge-target-line {
+  stroke-width: 1px;
+  stroke: #000;
+}
+
+/* Annotations module */
+.highcharts-annotation-label-box {
+  stroke-width: 1px;
+  stroke: #404048;
+  fill: #404048;
+  fill-opacity: 0.75;
+}
+
+.highcharts-annotation-label text {
+  fill: #e6e6e6;
+}
+
+/* Gantt */
+.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
+  cursor: pointer;
+}
+
+.highcharts-point-connecting-path {
+  fill: none;
+}
+
+.highcharts-grid-axis .highcharts-tick {
+  stroke-width: 1px;
+}
+
+.highcharts-grid-axis .highcharts-axis-line {
+  stroke-width: 1px;
+}

+ 33 - 0
app/public/js/Highstock-8.0.0/code/css/themes/grid-light.scss

@@ -0,0 +1,33 @@
+// Global font
+@import 'https://fonts.googleapis.com/css?family=Dosis:400,600';
+
+// Colors for data series and points.
+$colors: #7cb5ec #f7a35c #90ee7e #7798BF #aaeeee #ff0066 #eeaaee #55BF3B #DF5353 #7798BF #aaeeee;
+
+// Neutral colors
+$neutral-color-100: #404048;
+$neutral-color-80: #000;
+
+// Fonts
+$font-family: 'Dosis', Arial, Helvetica, sans-serif;
+$title-font-size: 16px;
+$legend-font-size: 13px;
+$axis-labels-font-size: 12px;
+
+// Tooltip
+$tooltip-border: 0px;
+$tooltip-background: rgba(219,219,216,0.8);
+
+// Axes
+$xaxis-grid-line: 1px !default;
+
+// Title
+.highcharts-title, .highcharts-subtitle, .highcharts-yaxis .highcharts-axis-title {
+	text-transform: uppercase;
+}
+
+.highcharts-title {
+	font-weight: bold;
+}
+
+@import '../highcharts';

+ 956 - 0
app/public/js/Highstock-8.0.0/code/css/themes/sand-signika.css

@@ -0,0 +1,956 @@
+@import 'https://fonts.googleapis.com/css?family=Signika:400,700';
+.highcharts-container {
+  background: url(https://www.highcharts.com/samples/graphics/sand.png);
+}
+
+.highcharts-boxplot-box {
+  fill: #505053;
+}
+
+.highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke: #D0D0D8;
+}
+
+.highcharts-scrollbar-track {
+  stroke: #C0C0C8;
+}
+
+.highcharts-title {
+  font-weight: bold;
+}
+
+.highcharts-button-box {
+  stroke-width: 1px;
+}
+
+/**
+ * @license Highcharts
+ *
+ * (c) 2009-2016 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+.highcharts-container {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+  text-align: left;
+  line-height: normal;
+  z-index: 0;
+  /* #1072 */
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+  font-family: "Signika", Arial, Helvetica, sans-serif;
+  font-size: 12px;
+}
+
+.highcharts-root {
+  display: block;
+}
+
+.highcharts-root text {
+  stroke-width: 0;
+}
+
+.highcharts-strong {
+  font-weight: bold;
+}
+
+.highcharts-emphasized {
+  font-style: italic;
+}
+
+.highcharts-anchor {
+  cursor: pointer;
+}
+
+.highcharts-background {
+  fill: none;
+}
+
+.highcharts-plot-border, .highcharts-plot-background {
+  fill: none;
+}
+
+.highcharts-label-box {
+  fill: none;
+}
+
+.highcharts-button-box {
+  fill: inherit;
+}
+
+.highcharts-tracker-line {
+  stroke-linejoin: round;
+  stroke: rgba(192, 192, 192, 0.0001);
+  stroke-width: 22;
+  fill: none;
+}
+
+.highcharts-tracker-area {
+  fill: rgba(192, 192, 192, 0.0001);
+  stroke-width: 0;
+}
+
+/* Titles */
+.highcharts-title {
+  fill: #000;
+  font-size: 16px;
+}
+
+.highcharts-subtitle {
+  fill: #666666;
+}
+
+/* Axes */
+.highcharts-axis-line {
+  fill: none;
+  stroke: #ccd6eb;
+}
+
+.highcharts-yaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-axis-title {
+  fill: #666666;
+}
+
+.highcharts-axis-labels {
+  fill: #666666;
+  cursor: default;
+  font-size: 12px;
+}
+
+.highcharts-grid-line {
+  fill: none;
+  stroke: #e6e6e6;
+}
+
+.highcharts-xaxis-grid .highcharts-grid-line {
+  stroke-width: 0px;
+}
+
+.highcharts-tick {
+  stroke: #ccd6eb;
+}
+
+.highcharts-yaxis .highcharts-tick {
+  stroke-width: 0;
+}
+
+.highcharts-minor-grid-line {
+  stroke: #f2f2f2;
+}
+
+.highcharts-crosshair-thin {
+  stroke-width: 1px;
+  stroke: #cccccc;
+}
+
+.highcharts-crosshair-category {
+  stroke: #ccd6eb;
+  stroke-opacity: 0.25;
+}
+
+/* Credits */
+.highcharts-credits {
+  cursor: pointer;
+  fill: #999999;
+  font-size: 0.7em;
+  transition: fill 250ms, font-size 250ms;
+}
+
+.highcharts-credits:hover {
+  fill: black;
+  font-size: 1em;
+}
+
+/* Tooltip */
+.highcharts-tooltip {
+  cursor: default;
+  pointer-events: none;
+  white-space: nowrap;
+  transition: stroke 150ms;
+}
+
+.highcharts-tooltip text {
+  fill: #000;
+}
+
+.highcharts-tooltip .highcharts-header {
+  font-size: 0.85em;
+}
+
+.highcharts-tooltip-box {
+  stroke-width: 0px;
+  fill: #fff;
+  fill-opacity: 0.85;
+}
+
+.highcharts-tooltip-box .highcharts-label-box {
+  fill: #fff;
+  fill-opacity: 0.85;
+}
+
+div.highcharts-tooltip {
+  filter: none;
+}
+
+.highcharts-selection-marker {
+  fill: #335cad;
+  fill-opacity: 0.25;
+}
+
+.highcharts-graph {
+  fill: none;
+  stroke-width: 2px;
+  stroke-linecap: round;
+  stroke-linejoin: round;
+}
+
+.highcharts-state-hover .highcharts-graph {
+  stroke-width: 3;
+}
+
+.highcharts-point-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-series-inactive {
+  opacity: 0.2;
+  transition: opacity 50ms;
+  /* quick in */
+}
+
+.highcharts-state-hover path {
+  transition: stroke-width 50ms;
+  /* quick in */
+}
+
+.highcharts-state-normal path {
+  transition: stroke-width 250ms;
+  /* slow out */
+}
+
+/* Legend hover affects points and series */
+g.highcharts-series,
+.highcharts-point,
+.highcharts-markers,
+.highcharts-data-labels {
+  transition: opacity 250ms;
+}
+
+.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
+.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
+.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
+.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
+  opacity: 0.2;
+}
+
+/* Series options */
+/* Default colors */
+.highcharts-color-0 {
+  fill: #f45b5b;
+  stroke: #f45b5b;
+}
+
+.highcharts-color-1 {
+  fill: #8085e9;
+  stroke: #8085e9;
+}
+
+.highcharts-color-2 {
+  fill: #8d4654;
+  stroke: #8d4654;
+}
+
+.highcharts-color-3 {
+  fill: #7798BF;
+  stroke: #7798BF;
+}
+
+.highcharts-color-4 {
+  fill: #aaeeee;
+  stroke: #aaeeee;
+}
+
+.highcharts-color-5 {
+  fill: #ff0066;
+  stroke: #ff0066;
+}
+
+.highcharts-color-6 {
+  fill: #eeaaee;
+  stroke: #eeaaee;
+}
+
+.highcharts-color-7 {
+  fill: #55BF3B;
+  stroke: #55BF3B;
+}
+
+.highcharts-color-8 {
+  fill: #DF5353;
+  stroke: #DF5353;
+}
+
+.highcharts-color-9 {
+  fill: #7798BF;
+  stroke: #7798BF;
+}
+
+.highcharts-color-10 {
+  fill: #aaeeee;
+  stroke: #aaeeee;
+}
+
+.highcharts-area {
+  fill-opacity: 0.75;
+  stroke-width: 0;
+}
+
+.highcharts-markers {
+  stroke-width: 1px;
+  stroke: none;
+}
+
+.highcharts-point {
+  stroke-width: 1px;
+}
+
+.highcharts-dense-data .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-data-label {
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+.highcharts-data-label-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-data-label text, text.highcharts-data-label {
+  fill: #000;
+}
+
+.highcharts-data-label-connector {
+  fill: none;
+}
+
+.highcharts-halo {
+  fill-opacity: 0.25;
+  stroke-width: 0;
+}
+
+.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
+.highcharts-markers .highcharts-point-select {
+  fill: #cccccc;
+  stroke: #fff;
+}
+
+.highcharts-column-series rect.highcharts-point {
+  stroke: none;
+}
+
+.highcharts-column-series .highcharts-point {
+  transition: fill-opacity 250ms;
+}
+
+.highcharts-column-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pie-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: none;
+}
+
+.highcharts-pie-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: none;
+}
+
+.highcharts-funnel-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-funnel-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-pyramid-series .highcharts-point {
+  stroke-linejoin: round;
+  stroke: none;
+}
+
+.highcharts-pyramid-series .highcharts-point-hover {
+  fill-opacity: 0.75;
+  transition: fill-opacity 50ms;
+}
+
+.highcharts-pyramid-series .highcharts-point-select {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.highcharts-solidgauge-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-treemap-series .highcharts-point {
+  stroke-width: 1px;
+  stroke: #e6e6e6;
+  transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
+}
+
+.highcharts-treemap-series .highcharts-point-hover {
+  stroke: #999999;
+  transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
+}
+
+.highcharts-treemap-series .highcharts-above-level {
+  display: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node {
+  fill: none;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive {
+  fill-opacity: 0.15;
+  cursor: pointer;
+}
+
+.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
+  fill-opacity: 0.75;
+}
+
+.highcharts-vector-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+.highcharts-windbarb-series .highcharts-point {
+  fill: none;
+  stroke-width: 2px;
+}
+
+/* Dumbbell/lollipop connector */
+.highcharts-lollipop-stem {
+  stroke: #fff;
+}
+
+/* Legend */
+.highcharts-legend-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item > text {
+  fill: #000;
+  font-weight: bold;
+  font-size: 13px;
+  cursor: pointer;
+  stroke-width: 0;
+}
+
+.highcharts-legend-item:hover text {
+  fill: #fff;
+}
+
+.highcharts-legend-item-hidden * {
+  fill: #cccccc !important;
+  stroke: #cccccc !important;
+  transition: fill 250ms;
+}
+
+.highcharts-legend-nav-active {
+  fill: #003399;
+  cursor: pointer;
+}
+
+.highcharts-legend-nav-inactive {
+  fill: #cccccc;
+}
+
+circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive {
+  /* tracker */
+  fill: rgba(192, 192, 192, 0.0001);
+}
+
+.highcharts-legend-title-box {
+  fill: none;
+  stroke-width: 0;
+}
+
+/* Bubble legend */
+.highcharts-bubble-legend-symbol {
+  stroke-width: 2;
+  fill-opacity: 0.5;
+}
+
+.highcharts-bubble-legend-connectors {
+  stroke-width: 1;
+}
+
+.highcharts-bubble-legend-labels {
+  fill: #000;
+}
+
+/* Loading */
+.highcharts-loading {
+  position: absolute;
+  background-color: none;
+  opacity: 0.5;
+  text-align: center;
+  z-index: 10;
+  transition: opacity 250ms;
+}
+
+.highcharts-loading-hidden {
+  height: 0 !important;
+  opacity: 0;
+  overflow: hidden;
+  transition: opacity 250ms, height 250ms step-end;
+}
+
+.highcharts-loading-inner {
+  font-weight: bold;
+  position: relative;
+  top: 45%;
+}
+
+/* Plot bands and polar pane backgrounds */
+.highcharts-plot-band, .highcharts-pane {
+  fill: #fff;
+  fill-opacity: 0.05;
+}
+
+.highcharts-plot-line {
+  fill: none;
+  stroke: #999999;
+  stroke-width: 1px;
+}
+
+/* Highcharts More and modules */
+.highcharts-boxplot-box {
+  fill: none;
+}
+
+.highcharts-boxplot-median {
+  stroke-width: 2px;
+}
+
+.highcharts-bubble-series .highcharts-point {
+  fill-opacity: 0.5;
+}
+
+.highcharts-errorbar-series .highcharts-point {
+  stroke: #fff;
+}
+
+.highcharts-gauge-series .highcharts-data-label-box {
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-gauge-series .highcharts-dial {
+  fill: #fff;
+  stroke-width: 0;
+}
+
+.highcharts-polygon-series .highcharts-graph {
+  fill: inherit;
+  stroke-width: 0;
+}
+
+.highcharts-waterfall-series .highcharts-graph {
+  stroke: #000;
+  stroke-dasharray: 1, 3;
+}
+
+.highcharts-sankey-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-sankey-series .highcharts-link {
+  transition: fill 250ms, fill-opacity 250ms;
+  fill-opacity: 0.5;
+}
+
+.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
+  transition: fill 50ms, fill-opacity 50ms;
+  fill-opacity: 1;
+}
+
+.highcharts-venn-series .highcharts-point {
+  fill-opacity: 0.75;
+  stroke: #cccccc;
+  transition: stroke 250ms, fill-opacity 250ms;
+}
+
+.highcharts-venn-series .highcharts-point-hover {
+  fill-opacity: 1;
+  stroke: #cccccc;
+}
+
+/* Highstock */
+.highcharts-navigator-mask-outside {
+  fill-opacity: 0;
+}
+
+.highcharts-navigator-mask-inside {
+  fill: #6685c2;
+  /* navigator.maskFill option */
+  fill-opacity: 0.25;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-outline {
+  stroke: #cccccc;
+  fill: none;
+}
+
+.highcharts-navigator-handle {
+  stroke: #cccccc;
+  fill: #f2f2f2;
+  cursor: ew-resize;
+}
+
+.highcharts-navigator-series {
+  fill: #f45b5b;
+  stroke: #f45b5b;
+}
+
+.highcharts-navigator-series .highcharts-graph {
+  stroke-width: 1px;
+}
+
+.highcharts-navigator-series .highcharts-area {
+  fill-opacity: 0.05;
+}
+
+.highcharts-navigator-xaxis .highcharts-axis-line {
+  stroke-width: 0;
+}
+
+.highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke-width: 1px;
+  stroke: #e6e6e6;
+}
+
+.highcharts-navigator-xaxis.highcharts-axis-labels {
+  fill: #999999;
+}
+
+.highcharts-navigator-yaxis .highcharts-grid-line {
+  stroke-width: 0;
+}
+
+.highcharts-scrollbar-thumb {
+  fill: #cccccc;
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-button {
+  fill: #e6e6e6;
+  stroke: #cccccc;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-arrow {
+  fill: #666666;
+}
+
+.highcharts-scrollbar-rifles {
+  stroke: #666666;
+  stroke-width: 1px;
+}
+
+.highcharts-scrollbar-track {
+  fill: #f2f2f2;
+  stroke: #C0C0C8;
+  stroke-width: 1px;
+}
+
+.highcharts-button {
+  fill: #fff;
+  stroke: #C0C0C8;
+  cursor: default;
+  stroke-width: 1px;
+  transition: fill 250ms;
+}
+
+.highcharts-button text {
+  fill: #000;
+}
+
+.highcharts-button-hover {
+  transition: fill 0ms;
+  fill: #e6e6e6;
+  stroke: #cccccc;
+}
+
+.highcharts-button-hover text {
+  fill: #000;
+}
+
+.highcharts-button-pressed {
+  font-weight: bold;
+  fill: #D0D0D8;
+  stroke: #cccccc;
+}
+
+.highcharts-button-pressed text {
+  fill: #000;
+  font-weight: bold;
+}
+
+.highcharts-button-disabled text {
+  fill: #000;
+}
+
+.highcharts-range-selector-buttons .highcharts-button {
+  stroke-width: 0px;
+}
+
+.highcharts-range-label rect {
+  fill: none;
+}
+
+.highcharts-range-label text {
+  fill: #666666;
+}
+
+.highcharts-range-input rect {
+  fill: none;
+}
+
+.highcharts-range-input text {
+  fill: #000;
+}
+
+.highcharts-range-input {
+  stroke-width: 1px;
+  stroke: #cccccc;
+}
+
+input.highcharts-range-selector {
+  position: absolute;
+  border: 0;
+  width: 1px;
+  /* Chrome needs a pixel to see it */
+  height: 1px;
+  padding: 0;
+  text-align: center;
+  left: -9em;
+  /* #4798 */
+}
+
+.highcharts-crosshair-label text {
+  fill: none;
+  font-size: 1.1em;
+}
+
+.highcharts-crosshair-label .highcharts-label-box {
+  fill: inherit;
+}
+
+.highcharts-candlestick-series .highcharts-point {
+  stroke: #fff;
+  stroke-width: 1px;
+}
+
+.highcharts-candlestick-series .highcharts-point-up {
+  fill: none;
+}
+
+.highcharts-ohlc-series .highcharts-point-hover {
+  stroke-width: 3px;
+}
+
+.highcharts-flags-series .highcharts-point .highcharts-label-box {
+  stroke: #999999;
+  fill: none;
+  transition: fill 250ms;
+}
+
+.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
+  stroke: #fff;
+  fill: #ccd6eb;
+}
+
+.highcharts-flags-series .highcharts-point text {
+  fill: #fff;
+  font-size: 0.9em;
+  font-weight: bold;
+}
+
+/* Highmaps */
+.highcharts-map-series .highcharts-point {
+  transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
+  stroke: #cccccc;
+}
+
+.highcharts-map-series .highcharts-point-hover {
+  transition: fill 0ms, fill-opacity 0ms;
+  fill-opacity: 0.5;
+  stroke-width: 2px;
+}
+
+.highcharts-mapline-series .highcharts-point {
+  fill: none;
+}
+
+.highcharts-heatmap-series .highcharts-point {
+  stroke-width: 0;
+}
+
+.highcharts-map-navigation {
+  font-size: 1.3em;
+  font-weight: bold;
+  text-align: center;
+}
+
+.highcharts-coloraxis {
+  stroke-width: 0;
+}
+
+.highcharts-coloraxis-marker {
+  fill: #999999;
+}
+
+.highcharts-null-point {
+  fill: #f7f7f7;
+}
+
+/* 3d charts */
+.highcharts-3d-frame {
+  fill: transparent;
+}
+
+/* Exporting module */
+.highcharts-contextbutton {
+  fill: #fff;
+  /* needed to capture hover */
+  stroke: none;
+  stroke-linecap: round;
+}
+
+.highcharts-contextbutton:hover {
+  fill: #e6e6e6;
+  stroke: #e6e6e6;
+}
+
+.highcharts-button-symbol {
+  stroke: #666666;
+  stroke-width: 3px;
+}
+
+.highcharts-menu {
+  border: 1px solid #999999;
+  background: none;
+  padding: 5px 0;
+  box-shadow: 3px 3px 10px #888;
+}
+
+.highcharts-menu-item {
+  padding: 0.5em 1em;
+  background: none;
+  color: #000;
+  cursor: pointer;
+  transition: background 250ms, color 250ms;
+}
+
+.highcharts-menu-item:hover {
+  background: #335cad;
+  color: none;
+}
+
+/* Drilldown module */
+.highcharts-drilldown-point {
+  cursor: pointer;
+}
+
+.highcharts-drilldown-data-label text,
+text.highcharts-drilldown-data-label,
+.highcharts-drilldown-axis-label {
+  cursor: pointer;
+  fill: #003399;
+  font-weight: bold;
+  text-decoration: underline;
+}
+
+/* No-data module */
+.highcharts-no-data text {
+  font-weight: bold;
+  font-size: 12px;
+  fill: #666666;
+}
+
+/* Drag-panes module */
+.highcharts-axis-resizer {
+  cursor: ns-resize;
+  stroke: black;
+  stroke-width: 2px;
+}
+
+/* Bullet type series */
+.highcharts-bullet-target {
+  stroke-width: 0;
+}
+
+/* Lineargauge type series */
+.highcharts-lineargauge-target {
+  stroke-width: 1px;
+  stroke: #000;
+}
+
+.highcharts-lineargauge-target-line {
+  stroke-width: 1px;
+  stroke: #000;
+}
+
+/* Annotations module */
+.highcharts-annotation-label-box {
+  stroke-width: 1px;
+  stroke: #fff;
+  fill: #fff;
+  fill-opacity: 0.75;
+}
+
+.highcharts-annotation-label text {
+  fill: #e6e6e6;
+}
+
+/* Gantt */
+.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
+  cursor: pointer;
+}
+
+.highcharts-point-connecting-path {
+  fill: none;
+}
+
+.highcharts-grid-axis .highcharts-tick {
+  stroke-width: 1px;
+}
+
+.highcharts-grid-axis .highcharts-axis-line {
+  stroke-width: 1px;
+}

+ 74 - 0
app/public/js/Highstock-8.0.0/code/css/themes/sand-signika.scss

@@ -0,0 +1,74 @@
+// Global font
+@import 'https://fonts.googleapis.com/css?family=Signika:400,700';
+
+// Chart background, point stroke for markers and columns etc
+$background-color: none;
+
+// Colors for data series and points.
+$colors: #f45b5b #8085e9 #8d4654 #7798BF #aaeeee #ff0066 #eeaaee #55BF3B #DF5353 #7798BF #aaeeee;
+
+// Neutral colors
+$neutral-color-100: #fff;
+$neutral-color-80: #000;
+
+// Data-labels
+$data-label-color: #000;
+
+// Fonts
+$font-family: 'Signika', Arial, Helvetica, sans-serif;
+$title-font-size: 16px;
+$legend-font-size: 13px;
+$axis-labels-font-size: 12px;
+
+// Tooltip
+$tooltip-border: 0px;
+$tooltip-background: $neutral-color-100;
+
+// Buttons
+$highcharts-button-background: $neutral-color-100;
+$highcharts-button-border: #C0C0C8;
+$highcharts-button-text:  #000;
+
+$highcharts-button-pressed-background: #D0D0D8;
+$highcharts-button-pressed-text:  #000;
+
+$context-button-background: $neutral-color-100;
+
+// Navigator
+$navigator-series-fill: #f45b5b;
+$navigator-series-border: #f45b5b;
+
+// Scrollbar
+$scrollbar-track-border: #C0C0C8;
+
+// General
+.highcharts-container {
+  background: url(https://www.highcharts.com/samples/graphics/sand.png);
+}
+
+// Boxplot
+.highcharts-boxplot-box {
+   fill: #505053;
+}
+
+// Navigator
+.highcharts-navigator-xaxis .highcharts-grid-line {
+  stroke: #D0D0D8;
+}
+
+// Scrollbar
+.highcharts-scrollbar-track {
+  stroke: #C0C0C8;
+}
+
+// Title
+.highcharts-title {
+	font-weight: bold;
+}
+
+// Buttons
+.highcharts-button-box {
+	stroke-width: 1px;
+}
+
+@import '../highcharts';

+ 141 - 0
app/public/js/Highstock-8.0.0/code/es-modules/annotations/ControlPoint.js

@@ -0,0 +1,141 @@
+import H from './../parts/Globals.js';
+import U from './../parts/Utilities.js';
+var extend = U.extend,
+    pick = U.pick;
+
+import eventEmitterMixin from './eventEmitterMixin.js';
+
+/**
+ * A control point class which is a connection between controllable
+ * transform methods and a user actions.
+ *
+ * @constructor
+ * @mixes eventEmitterMixin
+ * @memberOf Annotation
+ *
+ * @param {Highcharts.Chart} chart a chart instance
+ * @param {Object} target a controllable instance which is a target for
+ *        a control point
+ * @param {Annotation.ControlPoint.Options} options an options object
+ * @param {number} [index]
+ */
+function ControlPoint(chart, target, options, index) {
+    this.chart = chart;
+    this.target = target;
+    this.options = options;
+    this.index = pick(options.index, index);
+}
+
+/**
+ * @typedef {Object} Annotation.ControlPoint.Position
+ * @property {number} x
+ * @property {number} y
+ */
+
+/**
+ * @callback Annotation.ControlPoint.Positioner
+ * @param {Object} e event
+ * @param {Controllable} target
+ * @return {Annotation.ControlPoint.Position} position
+ */
+
+/**
+ * @typedef {Object} Annotation.ControlPoint.Options
+ * @property {string} symbol
+ * @property {number} width
+ * @property {number} height
+ * @property {Object} style
+ * @property {boolean} visible
+ * @property {Annotation.ControlPoint.Positioner} positioner
+ * @property {Object} events
+ */
+
+extend(
+    ControlPoint.prototype,
+    eventEmitterMixin
+);
+
+/**
+ * List of events for `anntation.options.events` that should not be
+ * added to `annotation.graphic` but to the `annotation`.
+ *
+ * @type {Array<string>}
+ */
+ControlPoint.prototype.nonDOMEvents = ['drag'];
+
+/**
+ * Set the visibility.
+ *
+ * @param {boolean} [visible]
+ **/
+ControlPoint.prototype.setVisibility = function (visible) {
+    this.graphic.attr('visibility', visible ? 'visible' : 'hidden');
+
+    this.options.visible = visible;
+};
+
+/**
+ * Render the control point.
+ */
+ControlPoint.prototype.render = function () {
+    var chart = this.chart,
+        options = this.options;
+
+    this.graphic = chart.renderer
+        .symbol(
+            options.symbol,
+            0,
+            0,
+            options.width,
+            options.height
+        )
+        .add(chart.controlPointsGroup)
+        .css(options.style);
+
+    this.setVisibility(options.visible);
+    this.addEvents();
+};
+
+/**
+ * Redraw the control point.
+ *
+ * @param {boolean} [animation]
+ */
+ControlPoint.prototype.redraw = function (animation) {
+    this.graphic[animation ? 'animate' : 'attr'](
+        this.options.positioner.call(this, this.target)
+    );
+};
+
+
+/**
+ * Destroy the control point.
+ */
+ControlPoint.prototype.destroy = function () {
+    eventEmitterMixin.destroy.call(this);
+
+    if (this.graphic) {
+        this.graphic = this.graphic.destroy();
+    }
+
+    this.chart = null;
+    this.target = null;
+    this.options = null;
+};
+
+/**
+ * Update the control point.
+ */
+ControlPoint.prototype.update = function (userOptions) {
+    var chart = this.chart,
+        target = this.target,
+        index = this.index,
+        options = H.merge(true, this.options, userOptions);
+
+    this.destroy();
+    this.constructor(chart, target, options, index);
+    this.render(chart.controlPointsGroup);
+    this.redraw();
+};
+
+export default ControlPoint;

+ 453 - 0
app/public/js/Highstock-8.0.0/code/es-modules/annotations/MockPoint.js

@@ -0,0 +1,453 @@
+import H from '../parts/Globals.js';
+
+import U from '../parts/Utilities.js';
+var defined = U.defined,
+    extend = U.extend;
+
+import '../parts/Axis.js';
+import '../parts/Series.js';
+
+/**
+ * A mock point label configuration.
+ *
+ * @interface Annotation.MockLabelOptionsObject
+ *//**
+ * X value translated to x axis scale
+ * @name Annotation.MockLabelOptionsObject#x
+ * @type {number|undefined}
+ *//**
+ * Y value translated to y axis scale
+ * @name Annotation.MockLabelOptionsObject#y
+ * @type {number|undefined}
+ *//**
+ * @name Annotation.MockLabelOptionsObject#point
+ * @type {Highcharts.Point}
+ */
+
+/**
+ * A mock point configuration.
+ *
+ * @interface Highcharts.MockPointOptionsObject
+ *//**
+ * x value for the point in xAxis scale or pixels
+ * @name Highcharts.MockPointOptionsObject#x
+ * @type {number}
+ *//**
+ * y value for the point in yAxis scale or pixels
+ * @name Highcharts.MockPointOptionsObject#y
+ * @type {number}
+ *//**
+ * xAxis index or id
+ * @name Highcharts.MockPointOptionsObject#xAxis
+ * @type {Highcharts.Axis|number|string|undefined}
+ *//**
+ * yAxis index or id
+ * @name Highcharts.MockPointOptionsObject#yAxis
+ * @property {Highcharts.Axis|number|string|undefined}
+ */
+
+/**
+ * A point-like object, a mock point or a point uses in series.
+ *
+ * @private
+ * @typedef {Highcharts.Point|Highcharts.MockPoint} Highcharts.PointLike
+ */
+
+/**
+ * A trimmed point object which imitates {@link Highchart.Point} class.
+ * It is created when there is a need of pointing to some chart's position
+ * using axis values or pixel values
+ *
+ * @private
+ * @class
+ * @name Highcharts.MockPoint
+ *
+ * @param {Highcharts.Chart} chart
+ *        The chart object
+ *
+ * @param {Highcharts.MockPointOptionsObject} options
+ *        The options object
+ */
+function MockPoint(chart, target, options) {
+    /**
+     * A mock series instance imitating a real series from a real point.
+     *
+     * @type {Object}
+     * @property {boolean} series.visible=true - whether a series is visible
+     * @property {Chart} series.chart - a chart instance
+     * @property {function} series.getPlotBox
+     */
+    this.series = {
+        visible: true,
+        chart: chart,
+        getPlotBox: H.Series.prototype.getPlotBox
+    };
+
+    /**
+     * @type {?Controllable}
+     */
+    this.target = target || null;
+
+    /**
+     * Options for the mock point.
+     *
+     * @type {Highcharts.MockPointOptionsObject}
+     */
+    this.options = options;
+
+    /**
+     * If an xAxis is set it represents the point's value in terms of the xAxis.
+     *
+     * @name Annotation.MockPoint#x
+     * @type {?number}
+     */
+
+    /**
+     * If an yAxis is set it represents the point's value in terms of the yAxis.
+     *
+     * @name Annotation.MockPoint#y
+     * @type {?number}
+     */
+
+    /**
+     * It represents the point's pixel x coordinate relative to its plot box.
+     *
+     * @name Annotation.MockPoint#plotX
+     * @type {?number}
+     */
+
+    /**
+     * It represents the point's pixel y position relative to its plot box.
+     *
+     * @name Annotation.MockPoint#plotY
+     * @type {?number}
+     */
+
+    /**
+     * Whether the point is inside the plot box.
+     *
+     * @name Annotation.MockPoint#isInside
+     * @type {boolean}
+     */
+
+    this.applyOptions(this.getOptions());
+}
+
+/**
+ * Create a mock point from a real Highcharts point.
+ *
+ * @param {Point} point
+ *
+ * @return {Annotation.MockPoint} a mock point instance.
+ */
+MockPoint.fromPoint = function (point) {
+    return new MockPoint(point.series.chart, null, {
+        x: point.x,
+        y: point.y,
+        xAxis: point.series.xAxis,
+        yAxis: point.series.yAxis
+    });
+};
+
+/**
+ * @typedef Annotation.MockPoint.Position
+ * @property {number} x
+ * @property {number} y
+ */
+
+/**
+ * Get the pixel position from the point like object.
+ *
+ * @param {Annotation.PointLike} point
+ * @param {boolean} [paneCoordinates]
+ *        whether the pixel position should be relative
+ *
+ * @return {Annotation.MockPoint.Position} pixel position
+ */
+MockPoint.pointToPixels = function (point, paneCoordinates) {
+    var series = point.series,
+        chart = series.chart,
+        x = point.plotX,
+        y = point.plotY,
+        plotBox;
+
+    if (chart.inverted) {
+        if (point.mock) {
+            x = point.plotY;
+            y = point.plotX;
+        } else {
+            x = chart.plotWidth - point.plotY;
+            y = chart.plotHeight - point.plotX;
+        }
+    }
+
+    if (series && !paneCoordinates) {
+        plotBox = series.getPlotBox();
+        x += plotBox.translateX;
+        y += plotBox.translateY;
+    }
+
+    return {
+        x: x,
+        y: y
+    };
+};
+
+/**
+ * Get fresh mock point options from the point like object.
+ *
+ * @param {Annotation.PointLike} point
+ *
+ * @return {Annotation.MockPoint.Options} mock point's options
+ */
+MockPoint.pointToOptions = function (point) {
+    return {
+        x: point.x,
+        y: point.y,
+        xAxis: point.series.xAxis,
+        yAxis: point.series.yAxis
+    };
+};
+
+extend(MockPoint.prototype, /** @lends Annotation.MockPoint# */ {
+    /**
+     * A flag indicating that a point is not the real one.
+     *
+     * @type {boolean}
+     * @default true
+     */
+    mock: true,
+
+    /**
+     * Check if the point has dynamic options.
+     *
+     * @return {boolean} A positive flag if the point has dynamic options.
+     */
+    hasDynamicOptions: function () {
+        return typeof this.options === 'function';
+    },
+
+    /**
+     * Get the point's options.
+     *
+     * @return {Annotation.MockPoint.Options} the mock point's options.
+     */
+    getOptions: function () {
+        return this.hasDynamicOptions() ?
+            this.options(this.target) :
+            this.options;
+    },
+
+    /**
+     * Apply options for the point.
+     *
+     * @param {Annotation.MockPoint.Options} options
+     */
+    applyOptions: function (options) {
+        this.command = options.command;
+
+        this.setAxis(options, 'x');
+        this.setAxis(options, 'y');
+
+        this.refresh();
+    },
+
+    /**
+     * Set x or y axis.
+     *
+     * @param {Annotation.MockPoint.Options} options
+     * @param {string} xOrY 'x' or 'y' string literal
+     */
+    setAxis: function (options, xOrY) {
+        var axisName = xOrY + 'Axis',
+            axisOptions = options[axisName],
+            chart = this.series.chart;
+
+        this.series[axisName] =
+            axisOptions instanceof H.Axis ?
+                axisOptions :
+                defined(axisOptions) ?
+                    chart[axisName][axisOptions] || chart.get(axisOptions) :
+                    null;
+    },
+
+    /**
+     * Transform the mock point to an anchor
+     * (relative position on the chart).
+     *
+     * @return {Array<number>} A quadruple of numbers which denotes x, y,
+     * width and height of the box
+     **/
+    toAnchor: function () {
+        var anchor = [this.plotX, this.plotY, 0, 0];
+
+        if (this.series.chart.inverted) {
+            anchor[0] = this.plotY;
+            anchor[1] = this.plotX;
+        }
+
+        return anchor;
+    },
+
+    /**
+     * @typedef {Object} Annotation.MockPoint.LabelConfig
+     * @property {number|undefined} x x value translated to x axis scale
+     * @property {number|undefined} y y value translated to y axis scale
+     * @property {Annotation.MockPoint} point instance of the point
+     */
+
+    /**
+     * Returns a label config object -
+     * the same as Highcharts.Point.prototype.getLabelConfig
+     *
+     * @return {Annotation.MockPoint.LabelConfig} the point's label config
+     */
+    getLabelConfig: function () {
+        return {
+            x: this.x,
+            y: this.y,
+            point: this
+        };
+    },
+
+    /**
+     * Check if the point is inside its pane.
+     *
+     * @return {boolean} A flag indicating whether the point is inside the pane.
+     */
+    isInsidePane: function () {
+        var plotX = this.plotX,
+            plotY = this.plotY,
+            xAxis = this.series.xAxis,
+            yAxis = this.series.yAxis,
+            isInside = true;
+
+        if (xAxis) {
+            isInside = defined(plotX) && plotX >= 0 && plotX <= xAxis.len;
+        }
+
+        if (yAxis) {
+            isInside =
+                isInside &&
+                defined(plotY) &&
+                plotY >= 0 && plotY <= yAxis.len;
+        }
+
+        return isInside;
+    },
+
+    /**
+     * Refresh point values and coordinates based on its options.
+     */
+    refresh: function () {
+        var series = this.series,
+            xAxis = series.xAxis,
+            yAxis = series.yAxis,
+            options = this.getOptions();
+
+        if (xAxis) {
+            this.x = options.x;
+            this.plotX = xAxis.toPixels(options.x, true);
+        } else {
+            this.x = null;
+            this.plotX = options.x;
+        }
+
+        if (yAxis) {
+            this.y = options.y;
+            this.plotY = yAxis.toPixels(options.y, true);
+        } else {
+            this.y = null;
+            this.plotY = options.y;
+        }
+
+        this.isInside = this.isInsidePane();
+    },
+
+    /**
+     * Translate the point.
+     *
+     * @param {number} [cx] origin x transformation
+     * @param {number} [cy] origin y transformation
+     * @param {number} dx translation for x coordinate
+     * @param {number} dy translation for y coordinate
+     **/
+    translate: function (cx, cy, dx, dy) {
+        if (!this.hasDynamicOptions()) {
+            this.plotX += dx;
+            this.plotY += dy;
+
+            this.refreshOptions();
+        }
+    },
+
+    /**
+     * Scale the point.
+     *
+     * @param {number} cx origin x transformation
+     * @param {number} cy origin y transformation
+     * @param {number} sx scale factor x
+     * @param {number} sy scale factor y
+     */
+    scale: function (cx, cy, sx, sy) {
+        if (!this.hasDynamicOptions()) {
+            var x = this.plotX * sx,
+                y = this.plotY * sy,
+                tx = (1 - sx) * cx,
+                ty = (1 - sy) * cy;
+
+            this.plotX = tx + x;
+            this.plotY = ty + y;
+
+            this.refreshOptions();
+        }
+    },
+
+    /**
+     * Rotate the point.
+     *
+     * @param {number} cx origin x rotation
+     * @param {number} cy origin y rotation
+     * @param {number} radians
+     */
+    rotate: function (cx, cy, radians) {
+        if (!this.hasDynamicOptions()) {
+            var cos = Math.cos(radians),
+                sin = Math.sin(radians),
+                x = this.plotX,
+                y = this.plotY,
+                tx,
+                ty;
+
+            x -= cx;
+            y -= cy;
+
+            tx = x * cos - y * sin;
+            ty = x * sin + y * cos;
+
+            this.plotX = tx + cx;
+            this.plotY = ty + cy;
+
+            this.refreshOptions();
+        }
+    },
+
+    /**
+     * Refresh point options based on its plot coordinates.
+     */
+    refreshOptions: function () {
+        var series = this.series,
+            xAxis = series.xAxis,
+            yAxis = series.yAxis;
+
+        this.x = this.options.x = xAxis ?
+            this.options.x = xAxis.toValue(this.plotX, true) :
+            this.plotX;
+
+        this.y = this.options.y = yAxis ?
+            yAxis.toValue(this.plotY, true) :
+            this.plotY;
+    }
+});
+
+export default MockPoint;

+ 1321 - 0
app/public/js/Highstock-8.0.0/code/es-modules/annotations/annotations.src.js

@@ -0,0 +1,1321 @@
+/* *
+ *
+ *  (c) 2009-2017 Highsoft, Black Label
+ *
+ *  License: www.highcharts.com/license
+ *
+ * */
+
+'use strict';
+
+import H from '../parts/Globals.js';
+
+import U from '../parts/Utilities.js';
+var defined = U.defined,
+    destroyObjectProperties = U.destroyObjectProperties,
+    erase = U.erase,
+    extend = U.extend,
+    pick = U.pick,
+    splat = U.splat,
+    wrap = U.wrap;
+
+import '../parts/Chart.js';
+import controllableMixin from './controllable/controllableMixin.js';
+import ControllableRect from './controllable/ControllableRect.js';
+import ControllableCircle from './controllable/ControllableCircle.js';
+import ControllablePath from './controllable/ControllablePath.js';
+import ControllableImage from './controllable/ControllableImage.js';
+import ControllableLabel from './controllable/ControllableLabel.js';
+import eventEmitterMixin from './eventEmitterMixin.js';
+import MockPoint from './MockPoint.js';
+import ControlPoint from './ControlPoint.js';
+
+var merge = H.merge,
+    addEvent = H.addEvent,
+    fireEvent = H.fireEvent,
+    find = H.find,
+    reduce = H.reduce,
+    chartProto = H.Chart.prototype;
+
+/* *********************************************************************
+ *
+ * ANNOTATION
+ *
+ ******************************************************************** */
+
+/**
+ * @typedef {
+ *          Annotation.ControllableCircle|
+ *          Annotation.ControllableImage|
+ *          Annotation.ControllablePath|
+ *          Annotation.ControllableRect
+ *          }
+ *          Annotation.Shape
+ */
+
+/**
+ * @typedef {Annotation.ControllableLabel} Annotation.Label
+ */
+
+/**
+ * An annotation class which serves as a container for items like labels or
+ * shapes. Created items are positioned on the chart either by linking them to
+ * existing points or created mock points
+ *
+ * @class
+ * @name Highcharts.Annotation
+ *
+ * @param {Highcharts.Chart} chart a chart instance
+ * @param {Highcharts.AnnotationsOptions} userOptions the options object
+ */
+var Annotation = H.Annotation = function (chart, userOptions) {
+    var labelsAndShapes;
+
+    /**
+     * The chart that the annotation belongs to.
+     *
+     * @type {Highcharts.Chart}
+     */
+    this.chart = chart;
+
+    /**
+     * The array of points which defines the annotation.
+     *
+     * @type {Array<Highcharts.Point>}
+     */
+    this.points = [];
+
+    /**
+     * The array of control points.
+     *
+     * @type {Array<Annotation.ControlPoint>}
+     */
+    this.controlPoints = [];
+
+    this.coll = 'annotations';
+
+    /**
+     * The array of labels which belong to the annotation.
+     *
+     * @type {Array<Annotation.Label>}
+     */
+    this.labels = [];
+
+    /**
+     * The array of shapes which belong to the annotation.
+     *
+     * @type {Array<Annotation.Shape>}
+     */
+    this.shapes = [];
+
+    /**
+     * The options for the annotations.
+     *
+     * @type {Highcharts.AnnotationsOptions}
+     */
+    this.options = merge(this.defaultOptions, userOptions);
+
+    /**
+     * The user options for the annotations.
+     *
+     * @type {Highcharts.AnnotationsOptions}
+     */
+    this.userOptions = userOptions;
+
+    // Handle labels and shapes - those are arrays
+    // Merging does not work with arrays (stores reference)
+    labelsAndShapes = this.getLabelsAndShapesOptions(
+        this.options,
+        userOptions
+    );
+    this.options.labels = labelsAndShapes.labels;
+    this.options.shapes = labelsAndShapes.shapes;
+
+    /**
+     * The callback that reports to the overlapping-labels module which
+     * labels it should account for.
+     *
+     * @name labelCollector
+     * @memberOf Annotation#
+     * @type {Function}
+     */
+
+    /**
+     * The group svg element.
+     *
+     * @name group
+     * @memberOf Annotation#
+     * @type {Highcharts.SVGElement}
+     */
+
+    /**
+     * The group svg element of the annotation's shapes.
+     *
+     * @name shapesGroup
+     * @memberOf Annotation#
+     * @type {Highcharts.SVGElement}
+     */
+
+    /**
+     * The group svg element of the annotation's labels.
+     *
+     * @name labelsGroup
+     * @memberOf Annotation#
+     * @type {Highcharts.SVGElement}
+     */
+
+    this.init(chart, this.options);
+};
+
+
+merge(
+    true,
+    Annotation.prototype,
+    controllableMixin,
+    eventEmitterMixin,
+    /** @lends Annotation# */
+    {
+
+        /**
+         * List of events for `annotation.options.events` that should not be
+         * added to `annotation.graphic` but to the `annotation`.
+         *
+         * @type {Array<string>}
+         */
+        nonDOMEvents: ['add', 'afterUpdate', 'drag', 'remove'],
+
+        /**
+         * A basic type of an annotation. It allows to add custom labels
+         * or shapes. The items  can be tied to points, axis coordinates
+         * or chart pixel coordinates.
+         *
+         * @sample highcharts/annotations/basic/
+         *         Basic annotations
+         * @sample highcharts/demo/annotations/
+         *         Advanced annotations
+         * @sample highcharts/css/annotations
+         *         Styled mode
+         * @sample highcharts/annotations-advanced/controllable
+         *         Controllable items
+         * @sample {highstock} stock/annotations/fibonacci-retracements
+         *         Custom annotation, Fibonacci retracement
+         *
+         * @type         {Array<*>}
+         * @since        6.0.0
+         * @requires     modules/annotations
+         * @optionparent annotations
+         */
+        defaultOptions: {
+
+            /**
+             * Sets an ID for an annotation. Can be user later when removing an
+             * annotation in [Chart#removeAnnotation(id)](
+             * /class-reference/Highcharts.Chart#removeAnnotation) method.
+             *
+             * @type      {string|number}
+             * @apioption annotations.id
+             */
+
+            /**
+             * Whether the annotation is visible.
+             *
+             * @sample highcharts/annotations/visible/
+             *         Set annotation visibility
+             */
+            visible: true,
+
+            /**
+             * Allow an annotation to be draggable by a user. Possible
+             * values are `"x"`, `"xy"`, `"y"` and `""` (disabled).
+             *
+             * @sample highcharts/annotations/draggable/
+             *         Annotations draggable: 'xy'
+             *
+             * @type       {string}
+             * @validvalue ["x", "xy", "y", ""]
+             */
+            draggable: 'xy',
+
+            /**
+             * Options for annotation's labels. Each label inherits options
+             * from the labelOptions object. An option from the labelOptions
+             * can be overwritten by config for a specific label.
+             *
+             * @requires modules/annotations
+             */
+            labelOptions: {
+
+                /**
+                 * The alignment of the annotation's label. If right,
+                 * the right side of the label should be touching the point.
+                 *
+                 * @sample highcharts/annotations/label-position/
+                 *         Set labels position
+                 *
+                 * @type {Highcharts.AlignValue}
+                 */
+                align: 'center',
+
+                /**
+                 * Whether to allow the annotation's labels to overlap.
+                 * To make the labels less sensitive for overlapping,
+                 * the can be set to 0.
+                 *
+                 * @sample highcharts/annotations/tooltip-like/
+                 *         Hide overlapping labels
+                 */
+                allowOverlap: false,
+
+                /**
+                 * The background color or gradient for the annotation's label.
+                 *
+                 * @sample highcharts/annotations/label-presentation/
+                 *         Set labels graphic options
+                 *
+                 * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+                 */
+                backgroundColor: 'rgba(0, 0, 0, 0.75)',
+
+                /**
+                 * The border color for the annotation's label.
+                 *
+                 * @sample highcharts/annotations/label-presentation/
+                 *         Set labels graphic options
+                 *
+                 * @type {Highcharts.ColorString}
+                 */
+                borderColor: 'black',
+
+                /**
+                 * The border radius in pixels for the annotaiton's label.
+                 *
+                 * @sample highcharts/annotations/label-presentation/
+                 *         Set labels graphic options
+                 */
+                borderRadius: 3,
+
+                /**
+                 * The border width in pixels for the annotation's label
+                 *
+                 * @sample highcharts/annotations/label-presentation/
+                 *         Set labels graphic options
+                 */
+                borderWidth: 1,
+
+                /**
+                 * A class name for styling by CSS.
+                 *
+                 * @sample highcharts/css/annotations
+                 *         Styled mode annotations
+                 *
+                 * @since 6.0.5
+                 */
+                className: '',
+
+                /**
+                 * Whether to hide the annotation's label
+                 * that is outside the plot area.
+                 *
+                 * @sample highcharts/annotations/label-crop-overflow/
+                 *         Crop or justify labels
+                 */
+                crop: false,
+
+                /**
+                 * The label's pixel distance from the point.
+                 *
+                 * @sample highcharts/annotations/label-position/
+                 *         Set labels position
+                 *
+                 * @type      {number}
+                 * @apioption annotations.labelOptions.distance
+                 */
+
+                /**
+                 * A
+                 * [format](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
+                 * string for the data label.
+                 *
+                 * @see [plotOptions.series.dataLabels.format](plotOptions.series.dataLabels.format.html)
+                 *
+                 * @sample highcharts/annotations/label-text/
+                 *         Set labels text
+                 *
+                 * @type      {string}
+                 * @apioption annotations.labelOptions.format
+                 */
+
+                /**
+                 * Alias for the format option.
+                 *
+                 * @see [format](annotations.labelOptions.format.html)
+                 *
+                 * @sample highcharts/annotations/label-text/
+                 *         Set labels text
+                 *
+                 * @type      {string}
+                 * @apioption annotations.labelOptions.text
+                 */
+
+                /**
+                 * Callback JavaScript function to format the annotation's
+                 * label. Note that if a `format` or `text` are defined, the
+                 * format or text take precedence and the formatter is ignored.
+                 * `This` refers to a point object.
+                 *
+                 * @sample highcharts/annotations/label-text/
+                 *         Set labels text
+                 *
+                 * @type    {Highcharts.FormatterCallbackFunction<Highcharts.Point>}
+                 * @default function () { return defined(this.y) ? this.y : 'Annotation label'; }
+                 */
+                formatter: function () {
+                    return defined(this.y) ? this.y : 'Annotation label';
+                },
+
+                /**
+                 * How to handle the annotation's label that flow outside the
+                 * plot area. The justify option aligns the label inside the
+                 * plot area.
+                 *
+                 * @sample highcharts/annotations/label-crop-overflow/
+                 *         Crop or justify labels
+                 *
+                 * @validvalue ["allow", "justify"]
+                 */
+                overflow: 'justify',
+
+                /**
+                 * When either the borderWidth or the backgroundColor is set,
+                 * this    is the padding within the box.
+                 *
+                 * @sample highcharts/annotations/label-presentation/
+                 *         Set labels graphic options
+                 */
+                padding: 5,
+
+                /**
+                 * The shadow of the box. The shadow can be an object
+                 * configuration containing `color`, `offsetX`, `offsetY`,
+                 * `opacity` and `width`.
+                 *
+                 * @sample highcharts/annotations/label-presentation/
+                 *         Set labels graphic options
+                 *
+                 * @type {boolean|Highcharts.ShadowOptionsObject}
+                 */
+                shadow: false,
+
+                /**
+                 * The name of a symbol to use for the border around the label.
+                 * Symbols are predefined functions on the Renderer object.
+                 *
+                 * @sample highcharts/annotations/shapes/
+                 *         Available shapes for labels
+                 */
+                shape: 'callout',
+
+                /**
+                 * Styles for the annotation's label.
+                 *
+                 * @see [plotOptions.series.dataLabels.style](plotOptions.series.dataLabels.style.html)
+                 *
+                 * @sample highcharts/annotations/label-presentation/
+                 *         Set labels graphic options
+                 *
+                 * @type {Highcharts.CSSObject}
+                 */
+                style: {
+                    /** @ignore */
+                    fontSize: '11px',
+                    /** @ignore */
+                    fontWeight: 'normal',
+                    /** @ignore */
+                    color: 'contrast'
+                },
+
+                /**
+                 * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+                 * to render the annotation's label.
+                 */
+                useHTML: false,
+
+                /**
+                 * The vertical alignment of the annotation's label.
+                 *
+                 * @sample highcharts/annotations/label-position/
+                 *         Set labels position
+                 *
+                 * @type {Highcharts.VerticalAlignValue}
+                 */
+                verticalAlign: 'bottom',
+
+                /**
+                 * The x position offset of the label relative to the point.
+                 * Note that if a `distance` is defined, the distance takes
+                 * precedence over `x` and `y` options.
+                 *
+                 * @sample highcharts/annotations/label-position/
+                 *         Set labels position
+                 */
+                x: 0,
+
+                /**
+                 * The y position offset of the label relative to the point.
+                 * Note that if a `distance` is defined, the distance takes
+                 * precedence over `x` and `y` options.
+                 *
+                 * @sample highcharts/annotations/label-position/
+                 *         Set labels position
+                 */
+                y: -16
+            },
+
+            /**
+             * An array of labels for the annotation. For options that apply to
+             * multiple labels, they can be added to the
+             * [labelOptions](annotations.labelOptions.html).
+             *
+             * @type      {Array<*>}
+             * @extends   annotations.labelOptions
+             * @apioption annotations.labels
+             */
+
+            /**
+             * This option defines the point to which the label will be
+             * connected. It can be either the point which exists in the
+             * series - it is referenced by the point's id - or a new point with
+             * defined x, y properties and optionally axes.
+             *
+             * @sample highcharts/annotations/mock-point/
+             *         Attach annotation to a mock point
+             *
+             * @type      {string|Highcharts.MockPointOptionsObject}
+             * @requires  modules/annotations
+             * @apioption annotations.labels.point
+             */
+
+            /**
+             * The x position of the point. Units can be either in axis
+             * or chart pixel coordinates.
+             *
+             * @type      {number}
+             * @apioption annotations.labels.point.x
+             */
+
+            /**
+             * The y position of the point. Units can be either in axis
+             * or chart pixel coordinates.
+             *
+             * @type      {number}
+             * @apioption annotations.labels.point.y
+             */
+
+            /**
+             * This number defines which xAxis the point is connected to. It
+             * refers to either the axis id or the index of the axis in the
+             * xAxis array. If the option is not configured or the axis is not
+             * found the point's x coordinate refers to the chart pixels.
+             *
+             * @type      {number|string}
+             * @apioption annotations.labels.point.xAxis
+             */
+
+            /**
+             * This number defines which yAxis the point is connected to. It
+             * refers to either the axis id or the index of the axis in the
+             * yAxis array. If the option is not configured or the axis is not
+             * found the point's y coordinate refers to the chart pixels.
+             *
+             * @type      {number|string}
+             * @apioption annotations.labels.point.yAxis
+             */
+
+
+            /**
+             * An array of shapes for the annotation. For options that apply to
+             * multiple shapes, then can be added to the
+             * [shapeOptions](annotations.shapeOptions.html).
+             *
+             * @type      {Array<*>}
+             * @extends   annotations.shapeOptions
+             * @apioption annotations.shapes
+             */
+
+            /**
+             * This option defines the point to which the shape will be
+             * connected. It can be either the point which exists in the
+             * series - it is referenced by the point's id - or a new point with
+             * defined x, y properties and optionally axes.
+             *
+             * @type      {string|Highcharts.MockPointOptionsObject}
+             * @extends   annotations.labels.point
+             * @apioption annotations.shapes.point
+             */
+
+            /**
+             * An array of points for the shape. This option is available for
+             * shapes which can use multiple points such as path. A point can be
+             * either a point object or a point's id.
+             *
+             * @see [annotations.shapes.point](annotations.shapes.point.html)
+             *
+             * @type      {Array<string|Highcharts.MockPointOptionsObject>}
+             * @extends   annotations.labels.point
+             * @apioption annotations.shapes.points
+             */
+
+            /**
+             * Id of the marker which will be drawn at the final vertex of the
+             * path. Custom markers can be defined in defs property.
+             *
+             * @see [defs.markers](defs.markers.html)
+             *
+             * @sample highcharts/annotations/custom-markers/
+             *         Define a custom marker for annotations
+             *
+             * @type      {string}
+             * @apioption annotations.shapes.markerEnd
+             */
+
+            /**
+             * Id of the marker which will be drawn at the first vertex of the
+             * path. Custom markers can be defined in defs property.
+             *
+             * @see [defs.markers](defs.markers.html)
+             *
+             * @sample {highcharts} highcharts/annotations/custom-markers/
+             *         Define a custom marker for annotations
+             *
+             * @type      {string}
+             * @apioption annotations.shapes.markerStart
+             */
+
+
+            /**
+             * Options for annotation's shapes. Each shape inherits options from
+             * the shapeOptions object. An option from the shapeOptions can be
+             * overwritten by config for a specific shape.
+             *
+             * @requires  modules/annotations
+             */
+            shapeOptions: {
+
+                /**
+                 * The width of the shape.
+                 *
+                 * @sample highcharts/annotations/shape/
+                 *         Basic shape annotation
+                 *
+                 * @type      {number}
+                 * @apioption annotations.shapeOptions.width
+                 **/
+
+                /**
+                 * The height of the shape.
+                 *
+                 * @sample highcharts/annotations/shape/
+                 *         Basic shape annotation
+                 *
+                 * @type      {number}
+                 * @apioption annotations.shapeOptions.height
+                 */
+
+                /**
+                 * The type of the shape, e.g. circle or rectangle.
+                 *
+                 * @sample highcharts/annotations/shape/
+                 *         Basic shape annotation
+                 *
+                 * @type      {string}
+                 * @default   'rect'
+                 * @apioption annotations.shapeOptions.type
+                 */
+
+                /**
+                 * The color of the shape's stroke.
+                 *
+                 * @sample highcharts/annotations/shape/
+                 *         Basic shape annotation
+                 *
+                 * @type {Highcharts.ColorString}
+                 */
+                stroke: 'rgba(0, 0, 0, 0.75)',
+
+                /**
+                 * The pixel stroke width of the shape.
+                 *
+                 * @sample highcharts/annotations/shape/
+                 *         Basic shape annotation
+                 */
+                strokeWidth: 1,
+
+                /**
+                 * The color of the shape's fill.
+                 *
+                 * @sample highcharts/annotations/shape/
+                 *         Basic shape annotation
+                 *
+                 * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+                 */
+                fill: 'rgba(0, 0, 0, 0.75)',
+
+                /**
+                 * The radius of the shape.
+                 *
+                 * @sample highcharts/annotations/shape/
+                 *         Basic shape annotation
+                 */
+                r: 0,
+
+                /**
+                 * Defines additional snapping area around an annotation
+                 * making this annotation to focus. Defined in pixels.
+                 */
+                snap: 2
+            },
+
+            /**
+             * Options for annotation's control points. Each control point
+             * inherits options from controlPointOptions object.
+             * Options from the controlPointOptions can be overwritten
+             * by options in a specific control point.
+             *
+             * @type      {Annotation.ControlPoint.Options}
+             * @requires  modules/annotations
+             * @apioption annotations.controlPointOptions
+             */
+            controlPointOptions: {
+
+                /**
+                 * @function {Annotation.ControlPoint.Positioner}
+                 * @apioption annotations.controlPointOptions.positioner
+                 */
+
+                symbol: 'circle',
+                width: 10,
+                height: 10,
+                style: {
+                    stroke: 'black',
+                    'stroke-width': 2,
+                    fill: 'white'
+                },
+                visible: false,
+                events: {}
+            },
+
+            /**
+             * Event callback when annotation is added to the chart.
+             *
+             * @type      {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
+             * @since     7.1.0
+             * @apioption annotations.events.add
+             */
+
+            /**
+             * Event callback when annotation is updated (e.g. drag and
+             * droppped or resized by control points).
+             *
+             * @type      {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
+             * @since     7.1.0
+             * @apioption annotations.events.afterUpdate
+             */
+
+            /**
+             * Event callback when annotation is removed from the chart.
+             *
+             * @type      {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
+             * @since     7.1.0
+             * @apioption annotations.events.remove
+             */
+
+            /**
+             * Events available in annotations.
+             *
+             * @requires modules/annotations
+             */
+            events: {},
+
+            /**
+             * The Z index of the annotation.
+             */
+            zIndex: 6
+
+        },
+
+        /**
+         * Initialize the annotation.
+         *
+         * @param {Highcharts.Chart}
+         *        The chart
+         * @param {Highcharts.AnnotationsOptions}
+         *        The user options for the annotation
+         */
+        init: function () {
+            this.linkPoints();
+            this.addControlPoints();
+            this.addShapes();
+            this.addLabels();
+            this.addClipPaths();
+            this.setLabelCollector();
+        },
+
+        getLabelsAndShapesOptions: function (baseOptions, newOptions) {
+            var mergedOptions = {};
+
+            ['labels', 'shapes'].forEach(function (name) {
+                if (baseOptions[name]) {
+                    mergedOptions[name] = splat(newOptions[name]).map(
+                        function (basicOptions, i) {
+                            return merge(baseOptions[name][i], basicOptions);
+                        }
+                    );
+                }
+            });
+
+            return mergedOptions;
+        },
+
+        addShapes: function () {
+            (this.options.shapes || []).forEach(function (shapeOptions, i) {
+                var shape = this.initShape(shapeOptions, i);
+
+                merge(true, this.options.shapes[i], shape.options);
+            }, this);
+        },
+
+        addLabels: function () {
+            (this.options.labels || []).forEach(function (labelsOptions, i) {
+                var labels = this.initLabel(labelsOptions, i);
+
+                merge(true, this.options.labels[i], labels.options);
+            }, this);
+        },
+
+        addClipPaths: function () {
+            this.setClipAxes();
+
+            if (this.clipXAxis && this.clipYAxis) {
+                this.clipRect = this.chart.renderer.clipRect(
+                    this.getClipBox()
+                );
+            }
+        },
+
+        setClipAxes: function () {
+            var xAxes = this.chart.xAxis,
+                yAxes = this.chart.yAxis,
+                linkedAxes = reduce(
+                    (this.options.labels || [])
+                        .concat(this.options.shapes || []),
+                    function (axes, labelOrShape) {
+                        return [
+                            xAxes[
+                                labelOrShape &&
+                                labelOrShape.point &&
+                                labelOrShape.point.xAxis
+                            ] || axes[0],
+                            yAxes[
+                                labelOrShape &&
+                                labelOrShape.point &&
+                                labelOrShape.point.yAxis
+                            ] || axes[1]
+                        ];
+                    },
+                    []
+                );
+
+            this.clipXAxis = linkedAxes[0];
+            this.clipYAxis = linkedAxes[1];
+        },
+
+        getClipBox: function () {
+            return {
+                x: this.clipXAxis.left,
+                y: this.clipYAxis.top,
+                width: this.clipXAxis.width,
+                height: this.clipYAxis.height
+            };
+        },
+
+        setLabelCollector: function () {
+            var annotation = this;
+
+            annotation.labelCollector = function () {
+                return annotation.labels.reduce(
+                    function (labels, label) {
+                        if (!label.options.allowOverlap) {
+                            labels.push(label.graphic);
+                        }
+
+                        return labels;
+                    },
+                    []
+                );
+            };
+
+            annotation.chart.labelCollectors.push(
+                annotation.labelCollector
+            );
+        },
+
+        /**
+         * Set an annotation options.
+         *
+         * @param {Highcharts.AnnotationsOptions} - user options for an annotation
+         */
+        setOptions: function (userOptions) {
+            this.options = merge(this.defaultOptions, userOptions);
+        },
+
+        redraw: function (animation) {
+            this.linkPoints();
+
+            if (!this.graphic) {
+                this.render();
+            }
+
+            if (this.clipRect) {
+                this.clipRect.animate(this.getClipBox());
+            }
+
+            this.redrawItems(this.shapes, animation);
+            this.redrawItems(this.labels, animation);
+
+
+            controllableMixin.redraw.call(this, animation);
+        },
+
+        /**
+         * @param {Array<(Annotation.Label|Annotation.Shape)>} items
+         * @param {boolean} [animation]
+         */
+        redrawItems: function (items, animation) {
+            var i = items.length;
+
+            // needs a backward loop
+            // labels/shapes array might be modified
+            // due to destruction of the item
+            while (i--) {
+                this.redrawItem(items[i], animation);
+            }
+        },
+
+        render: function () {
+            var renderer = this.chart.renderer;
+
+            this.graphic = renderer
+                .g('annotation')
+                .attr({
+                    zIndex: this.options.zIndex,
+                    visibility: this.options.visible ?
+                        'visible' :
+                        'hidden'
+                })
+                .add();
+
+            this.shapesGroup = renderer
+                .g('annotation-shapes')
+                .add(this.graphic)
+                .clip(this.chart.plotBoxClip);
+
+            this.labelsGroup = renderer
+                .g('annotation-labels')
+                .attr({
+                    // hideOverlappingLabels requires translation
+                    translateX: 0,
+                    translateY: 0
+                })
+                .add(this.graphic);
+
+            if (this.clipRect) {
+                this.graphic.clip(this.clipRect);
+            }
+
+            this.addEvents();
+
+            controllableMixin.render.call(this);
+        },
+
+        /**
+         * Set the annotation's visibility.
+         *
+         * @param {Boolean} [visible] - Whether to show or hide an annotation.
+         * If the param is omitted, the annotation's visibility is toggled.
+         */
+        setVisibility: function (visibility) {
+            var options = this.options,
+                visible = pick(visibility, !options.visible);
+
+            this.graphic.attr(
+                'visibility',
+                visible ? 'visible' : 'hidden'
+            );
+
+            if (!visible) {
+                this.setControlPointsVisibility(false);
+            }
+
+            options.visible = visible;
+        },
+
+        setControlPointsVisibility: function (visible) {
+            var setItemControlPointsVisibility = function (item) {
+                item.setControlPointsVisibility(visible);
+            };
+
+            controllableMixin.setControlPointsVisibility.call(
+                this,
+                visible
+            );
+
+            this.shapes.forEach(setItemControlPointsVisibility);
+            this.labels.forEach(setItemControlPointsVisibility);
+        },
+
+        /**
+         * Destroy the annotation. This function does not touch the chart
+         * that the annotation belongs to (all annotations are kept in
+         * the chart.annotations array) - it is recommended to use
+         * {@link Highcharts.Chart#removeAnnotation} instead.
+         */
+        destroy: function () {
+            var chart = this.chart,
+                destroyItem = function (item) {
+                    item.destroy();
+                };
+
+            this.labels.forEach(destroyItem);
+            this.shapes.forEach(destroyItem);
+
+            this.clipXAxis = null;
+            this.clipYAxis = null;
+
+            erase(chart.labelCollectors, this.labelCollector);
+
+            eventEmitterMixin.destroy.call(this);
+            controllableMixin.destroy.call(this);
+
+            destroyObjectProperties(this, chart);
+        },
+
+        /**
+         * See {@link Highcharts.Chart#removeAnnotation}.
+         */
+        remove: function () {
+            // Let chart.update() remove annoations on demand
+            return this.chart.removeAnnotation(this);
+        },
+
+        update: function (userOptions) {
+            var chart = this.chart,
+                labelsAndShapes = this.getLabelsAndShapesOptions(
+                    this.userOptions,
+                    userOptions
+                ),
+                userOptionsIndex = chart.annotations.indexOf(this),
+                options = H.merge(true, this.userOptions, userOptions);
+
+            options.labels = labelsAndShapes.labels;
+            options.shapes = labelsAndShapes.shapes;
+
+            this.destroy();
+            this.constructor(chart, options);
+
+            // Update options in chart options, used in exporting (#9767):
+            chart.options.annotations[userOptionsIndex] = options;
+
+            this.isUpdating = true;
+            this.redraw();
+            this.isUpdating = false;
+            fireEvent(this, 'afterUpdate');
+        },
+
+        /* *************************************************************
+         * ITEM SECTION
+         * Contains methods for handling a single item in an annotation
+         **************************************************************** */
+
+        /**
+         * Initialisation of a single shape
+         *
+         * @param {Object} shapeOptions - a confg object for a single shape
+         */
+        initShape: function (shapeOptions, index) {
+            var options = merge(
+                    this.options.shapeOptions,
+                    {
+                        controlPointOptions: this.options.controlPointOptions
+                    },
+                    shapeOptions
+                ),
+                shape = new Annotation.shapesMap[options.type](
+                    this,
+                    options,
+                    index
+                );
+
+            shape.itemType = 'shape';
+
+            this.shapes.push(shape);
+
+            return shape;
+        },
+
+        /**
+         * Initialisation of a single label
+         *
+         * @param {Object} labelOptions
+         **/
+        initLabel: function (labelOptions, index) {
+            var options = merge(
+                    this.options.labelOptions,
+                    {
+                        controlPointOptions: this.options.controlPointOptions
+                    },
+                    labelOptions
+                ),
+                label = new ControllableLabel(
+                    this,
+                    options,
+                    index
+                );
+
+            label.itemType = 'label';
+
+            this.labels.push(label);
+
+            return label;
+        },
+
+        /**
+         * Redraw a single item.
+         *
+         * @param {Annotation.Label|Annotation.Shape} item
+         * @param {boolean} [animation]
+         */
+        redrawItem: function (item, animation) {
+            item.linkPoints();
+
+            if (!item.shouldBeDrawn()) {
+                this.destroyItem(item);
+            } else {
+                if (!item.graphic) {
+                    this.renderItem(item);
+                }
+
+                item.redraw(
+                    pick(animation, true) && item.graphic.placed
+                );
+
+                if (item.points.length) {
+                    this.adjustVisibility(item);
+                }
+            }
+        },
+
+        /**
+         * Hide or show annotaiton attached to points.
+         *
+         * @param {Annotation.Label|Annotation.Shape} item
+         */
+
+        adjustVisibility: function (item) { // #9481
+            var hasVisiblePoints = false,
+                label = item.graphic;
+
+            item.points.forEach(function (point) {
+                if (
+                    point.series.visible !== false &&
+                    point.visible !== false
+                ) {
+                    hasVisiblePoints = true;
+                }
+            });
+
+            if (!hasVisiblePoints) {
+                label.hide();
+
+            } else if (label.visibility === 'hidden') {
+                label.show();
+            }
+        },
+
+        /**
+         * Destroy a single item.
+         *
+         * @param {Annotation.Label|Annotation.Shape} item
+         */
+        destroyItem: function (item) {
+            // erase from shapes or labels array
+            erase(this[item.itemType + 's'], item);
+            item.destroy();
+        },
+
+        /**
+         * @private
+         */
+        renderItem: function (item) {
+            item.render(
+                item.itemType === 'label' ?
+                    this.labelsGroup :
+                    this.shapesGroup
+            );
+        }
+    }
+);
+
+/**
+ * An object uses for mapping between a shape type and a constructor.
+ * To add a new shape type extend this object with type name as a key
+ * and a constructor as its value.
+ */
+Annotation.shapesMap = {
+    'rect': ControllableRect,
+    'circle': ControllableCircle,
+    'path': ControllablePath,
+    'image': ControllableImage
+};
+
+Annotation.types = {};
+
+Annotation.MockPoint = MockPoint;
+Annotation.ControlPoint = ControlPoint;
+
+H.extendAnnotation = function (
+    Constructor,
+    BaseConstructor,
+    prototype,
+    defaultOptions
+) {
+    BaseConstructor = BaseConstructor || Annotation;
+
+    merge(
+        true,
+        Constructor.prototype,
+        BaseConstructor.prototype,
+        prototype
+    );
+
+    Constructor.prototype.defaultOptions = merge(
+        Constructor.prototype.defaultOptions,
+        defaultOptions || {}
+    );
+};
+
+/* *********************************************************************
+ *
+ * EXTENDING CHART PROTOTYPE
+ *
+ ******************************************************************** */
+
+extend(chartProto, /** @lends Highcharts.Chart# */ {
+    initAnnotation: function (userOptions) {
+        var Constructor =
+            Annotation.types[userOptions.type] || Annotation,
+            annotation = new Constructor(this, userOptions);
+
+        this.annotations.push(annotation);
+
+        return annotation;
+    },
+
+    /**
+     * Add an annotation to the chart after render time.
+     *
+     * @param  {Highcharts.AnnotationsOptions} options
+     *         The annotation options for the new, detailed annotation.
+     * @param {boolean} [redraw]
+     *
+     * @return {Highcharts.Annotation} - The newly generated annotation.
+     */
+    addAnnotation: function (userOptions, redraw) {
+        var annotation = this.initAnnotation(userOptions);
+
+        this.options.annotations.push(annotation.options);
+
+        if (pick(redraw, true)) {
+            annotation.redraw();
+        }
+
+        return annotation;
+    },
+
+    /**
+     * Remove an annotation from the chart.
+     *
+     * @param {String|Number|Annotation} idOrAnnotation - The annotation's id or
+     *      direct annotation object.
+     */
+    removeAnnotation: function (idOrAnnotation) {
+        var annotations = this.annotations,
+            annotation = idOrAnnotation.coll === 'annotations' ?
+                idOrAnnotation :
+                find(
+                    annotations,
+                    function (annotation) {
+                        return annotation.options.id === idOrAnnotation;
+                    }
+                );
+
+        if (annotation) {
+            fireEvent(annotation, 'remove');
+            erase(this.options.annotations, annotation.options);
+            erase(annotations, annotation);
+            annotation.destroy();
+        }
+    },
+
+    drawAnnotations: function () {
+        this.plotBoxClip.attr(this.plotBox);
+
+        this.annotations.forEach(function (annotation) {
+            annotation.redraw();
+        });
+    }
+});
+
+// Let chart.update() update annotations
+chartProto.collectionsWithUpdate.push('annotations');
+
+// Let chart.update() create annoations on demand
+chartProto.collectionsWithInit.annotations = [chartProto.addAnnotation];
+
+chartProto.callbacks.push(function (chart) {
+    chart.annotations = [];
+
+    if (!chart.options.annotations) {
+        chart.options.annotations = [];
+    }
+
+    chart.plotBoxClip = this.renderer.clipRect(this.plotBox);
+
+    chart.controlPointsGroup = chart.renderer
+        .g('control-points')
+        .attr({ zIndex: 99 })
+        .clip(chart.plotBoxClip)
+        .add();
+
+    chart.options.annotations.forEach(function (annotationOptions, i) {
+        var annotation = chart.initAnnotation(annotationOptions);
+
+        chart.options.annotations[i] = annotation.options;
+    });
+
+    chart.drawAnnotations();
+    addEvent(chart, 'redraw', chart.drawAnnotations);
+    addEvent(chart, 'destroy', function () {
+        chart.plotBoxClip.destroy();
+        chart.controlPointsGroup.destroy();
+    });
+});
+
+wrap(
+    H.Pointer.prototype,
+    'onContainerMouseDown',
+    function (proceed) {
+        if (!this.chart.hasDraggedAnnotation) {
+            proceed.apply(this, Array.prototype.slice.call(arguments, 1));
+        }
+    }
+);

+ 84 - 0
app/public/js/Highstock-8.0.0/code/es-modules/annotations/controllable/ControllableCircle.js

@@ -0,0 +1,84 @@
+'use strict';
+import H from './../../parts/Globals.js';
+import './../../parts/Utilities.js';
+import controllableMixin from './controllableMixin.js';
+import ControllablePath from './ControllablePath.js';
+
+/**
+ * A controllable circle class.
+ *
+ * @constructor
+ * @mixes Annotation.controllableMixin
+ * @memberOf Annotation
+ *
+ * @param {Highcharts.Annotation} annotation an annotation instance
+ * @param {Object} options a shape's options
+ * @param {number} index of the circle
+ **/
+function ControllableCircle(annotation, options, index) {
+    this.init(annotation, options, index);
+    this.collection = 'shapes';
+}
+
+/**
+ * A map object which allows to map options attributes to element attributes.
+ */
+ControllableCircle.attrsMap = H.merge(ControllablePath.attrsMap, {
+    r: 'r'
+});
+
+H.merge(
+    true,
+    ControllableCircle.prototype,
+    controllableMixin, /** @lends Annotation.ControllableCircle# */ {
+        /**
+         * @type 'circle'
+         */
+        type: 'circle',
+
+        translate: controllableMixin.translateShape,
+
+        render: function (parent) {
+            var attrs = this.attrsFromOptions(this.options);
+
+            this.graphic = this.annotation.chart.renderer
+                .circle(0, -9e9, 0)
+                .attr(attrs)
+                .add(parent);
+
+            controllableMixin.render.call(this);
+        },
+
+        redraw: function (animation) {
+            var position = this.anchor(this.points[0]).absolutePosition;
+
+            if (position) {
+                this.graphic[animation ? 'animate' : 'attr']({
+                    x: position.x,
+                    y: position.y,
+                    r: this.options.r
+                });
+            } else {
+                this.graphic.attr({
+                    x: 0,
+                    y: -9e9
+                });
+            }
+
+            this.graphic.placed = Boolean(position);
+
+            controllableMixin.redraw.call(this, animation);
+        },
+
+        /**
+         * Set the radius.
+         *
+         * @param {number} r a radius to be set
+         */
+        setRadius: function (r) {
+            this.options.r = r;
+        }
+    }
+);
+
+export default ControllableCircle;

+ 93 - 0
app/public/js/Highstock-8.0.0/code/es-modules/annotations/controllable/ControllableImage.js

@@ -0,0 +1,93 @@
+'use strict';
+import H from './../../parts/Globals.js';
+import './../../parts/Utilities.js';
+import controllableMixin from './controllableMixin.js';
+import ControllableLabel from './ControllableLabel.js';
+
+/**
+ * A controllable image class.
+ *
+ * @class
+ * @mixes Annotation.controllableMixin
+ * @memberOf Annotation
+ *
+ * @param {Highcharts.Annotation} annotation - an annotation instance
+ * @param {Object} options a controllable's options
+ * @param {number} index of the image
+ **/
+function ControllableImage(annotation, options, index) {
+    this.init(annotation, options, index);
+    this.collection = 'shapes';
+}
+
+/**
+ * @typedef {Object} Annotation.ControllableImage.AttrsMap
+ * @property {string} width=width
+ * @property {string} height=height
+ * @property {string} zIndex=zIndex
+ */
+
+/**
+ * A map object which allows to map options attributes to element attributes
+ *
+ * @type {Annotation.ControllableImage.AttrsMap}
+ */
+ControllableImage.attrsMap = {
+    width: 'width',
+    height: 'height',
+    zIndex: 'zIndex'
+};
+
+H.merge(
+    true,
+    ControllableImage.prototype,
+    controllableMixin, /** @lends Annotation.ControllableImage# */ {
+        /**
+         * @type 'image'
+         */
+        type: 'image',
+
+        translate: controllableMixin.translateShape,
+
+        render: function (parent) {
+            var attrs = this.attrsFromOptions(this.options),
+                options = this.options;
+
+            this.graphic = this.annotation.chart.renderer
+                .image(options.src, 0, -9e9, options.width, options.height)
+                .attr(attrs)
+                .add(parent);
+
+            this.graphic.width = options.width;
+            this.graphic.height = options.height;
+
+            controllableMixin.render.call(this);
+        },
+
+        redraw: function (animation) {
+            var anchor = this.anchor(this.points[0]),
+                position = ControllableLabel.prototype.position.call(
+                    this,
+                    anchor
+                );
+
+            if (position) {
+                this.graphic[animation ? 'animate' : 'attr']({
+                    x: position.x,
+                    y: position.y
+                });
+            } else {
+                this.graphic.attr({
+                    x: 0,
+                    y: -9e9
+                });
+            }
+
+            this.graphic.placed = Boolean(position);
+
+            controllableMixin.redraw.call(this, animation);
+        }
+    }
+);
+
+export default ControllableImage;

+ 509 - 0
app/public/js/Highstock-8.0.0/code/es-modules/annotations/controllable/ControllableLabel.js

@@ -0,0 +1,509 @@
+'use strict';
+import H from './../../parts/Globals.js';
+
+import U from './../../parts/Utilities.js';
+var extend = U.extend,
+    isNumber = U.isNumber,
+    pick = U.pick;
+
+import './../../parts/SvgRenderer.js';
+import controllableMixin from './controllableMixin.js';
+import MockPoint from './../MockPoint.js';
+
+
+/**
+ * @private
+ * @interface Highcharts.AnnotationAnchorObject
+ *//**
+ * Relative to the plot area position
+ * @name Highcharts.AnnotationAnchorObject#relativePosition
+ * @type {Highcharts.AnnotationAnchorPositionObject}
+ *//**
+ * Absolute position
+ * @name Highcharts.AnnotationAnchorObject#absolutePosition
+ * @type {Highcharts.AnnotationAnchorPositionObject}
+ */
+
+/**
+ * An object which denotes an anchor position
+ *
+ * @private
+ * @interface Highcharts.AnnotationAnchorPositionObject
+ *//**
+ * @name Highcharts.AnnotationAnchorPositionObject#x
+ * @property {number}
+ *//**
+ * @name Highcharts.AnnotationAnchorPositionObject#y
+ * @property {number}
+ *//**
+ * @name Highcharts.AnnotationAnchorPositionObject#height
+ * @property {number}
+ *//**
+ * @name Highcharts.AnnotationAnchorPositionObject#width
+ * @property {number}
+ */
+
+/**
+ * A controllable label class.
+ *
+ * @private
+ * @class
+ * @name Annotation.ControllableLabel
+ *
+ * @mixes Annotation.controllableMixin
+ *
+ * @param {Highcharts.Annotation} annotation an annotation instance
+ * @param {object} options a label's options
+ * @param {number} index of the label
+ **/
+function ControllableLabel(annotation, options, index) {
+    this.init(annotation, options, index);
+    this.collection = 'labels';
+}
+
+/**
+ * Shapes which do not have background - the object is used for proper
+ * setting of the contrast color.
+ *
+ * @type {Array<string>}
+ */
+ControllableLabel.shapesWithoutBackground = ['connector'];
+
+/**
+ * Returns new aligned position based alignment options and box to align to.
+ * It is almost a one-to-one copy from SVGElement.prototype.align
+ * except it does not use and mutate an element
+ *
+ * @param {Object} alignOptions
+ * @param {Object} box
+ * @return {Annotation.controllableMixin.Position} aligned position
+ */
+ControllableLabel.alignedPosition = function (alignOptions, box) {
+    var align = alignOptions.align,
+        vAlign = alignOptions.verticalAlign,
+        x = (box.x || 0) + (alignOptions.x || 0),
+        y = (box.y || 0) + (alignOptions.y || 0),
+
+        alignFactor,
+        vAlignFactor;
+
+    if (align === 'right') {
+        alignFactor = 1;
+    } else if (align === 'center') {
+        alignFactor = 2;
+    }
+    if (alignFactor) {
+        x += (box.width - (alignOptions.width || 0)) / alignFactor;
+    }
+
+    if (vAlign === 'bottom') {
+        vAlignFactor = 1;
+    } else if (vAlign === 'middle') {
+        vAlignFactor = 2;
+    }
+    if (vAlignFactor) {
+        y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
+    }
+
+    return {
+        x: Math.round(x),
+        y: Math.round(y)
+    };
+};
+
+/**
+ * Returns new alignment options for a label if the label is outside the
+ * plot area. It is almost a one-to-one copy from
+ * Series.prototype.justifyDataLabel except it does not mutate the label and
+ * it works with absolute instead of relative position.
+ *
+ * @param {Object} label
+ * @param {Object} alignOptions
+ * @param {Object} alignAttr
+ * @return {Object} justified options
+ **/
+ControllableLabel.justifiedOptions = function (
+    chart,
+    label,
+    alignOptions,
+    alignAttr
+) {
+    var align = alignOptions.align,
+        verticalAlign = alignOptions.verticalAlign,
+        padding = label.box ? 0 : (label.padding || 0),
+        bBox = label.getBBox(),
+        off,
+
+        options = {
+            align: align,
+            verticalAlign: verticalAlign,
+            x: alignOptions.x,
+            y: alignOptions.y,
+            width: label.width,
+            height: label.height
+        },
+
+        x = alignAttr.x - chart.plotLeft,
+        y = alignAttr.y - chart.plotTop;
+
+    // Off left
+    off = x + padding;
+    if (off < 0) {
+        if (align === 'right') {
+            options.align = 'left';
+        } else {
+            options.x = -off;
+        }
+    }
+
+    // Off right
+    off = x + bBox.width - padding;
+    if (off > chart.plotWidth) {
+        if (align === 'left') {
+            options.align = 'right';
+        } else {
+            options.x = chart.plotWidth - off;
+        }
+    }
+
+    // Off top
+    off = y + padding;
+    if (off < 0) {
+        if (verticalAlign === 'bottom') {
+            options.verticalAlign = 'top';
+        } else {
+            options.y = -off;
+        }
+    }
+
+    // Off bottom
+    off = y + bBox.height - padding;
+    if (off > chart.plotHeight) {
+        if (verticalAlign === 'top') {
+            options.verticalAlign = 'bottom';
+        } else {
+            options.y = chart.plotHeight - off;
+        }
+    }
+
+    return options;
+};
+
+/**
+ * @typedef {Object} Annotation.ControllableLabel.AttrsMap
+ * @property {string} backgroundColor=fill
+ * @property {string} borderColor=stroke
+ * @property {string} borderWidth=stroke-width
+ * @property {string} zIndex=zIndex
+ * @property {string} borderRadius=r
+ * @property {string} padding=padding
+ */
+
+/**
+ * A map object which allows to map options attributes to element attributes
+ *
+ * @type {Annotation.ControllableLabel.AttrsMap}
+ */
+ControllableLabel.attrsMap = {
+    backgroundColor: 'fill',
+    borderColor: 'stroke',
+    borderWidth: 'stroke-width',
+    zIndex: 'zIndex',
+    borderRadius: 'r',
+    padding: 'padding'
+};
+
+H.merge(
+    true,
+    ControllableLabel.prototype,
+    controllableMixin, /** @lends Annotation.ControllableLabel# */ {
+        /**
+         * Translate the point of the label by deltaX and deltaY translations.
+         * The point is the label's anchor.
+         *
+         * @param {number} dx translation for x coordinate
+         * @param {number} dy translation for y coordinate
+         **/
+        translatePoint: function (dx, dy) {
+            controllableMixin.translatePoint.call(this, dx, dy, 0);
+        },
+
+        /**
+         * Translate x and y position relative to the label's anchor.
+         *
+         * @param {number} dx translation for x coordinate
+         * @param {number} dy translation for y coordinate
+         **/
+        translate: function (dx, dy) {
+            var chart = this.annotation.chart,
+                // Annotation.options
+                labelOptions = this.annotation.userOptions,
+                // Chart.options.annotations
+                annotationIndex = chart.annotations.indexOf(this.annotation),
+                chartAnnotations = chart.options.annotations,
+                chartOptions = chartAnnotations[annotationIndex],
+                temp;
+
+            if (chart.inverted) {
+                temp = dx;
+                dx = dy;
+                dy = temp;
+            }
+
+            // Local options:
+            this.options.x += dx;
+            this.options.y += dy;
+
+            // Options stored in chart:
+            chartOptions[this.collection][this.index].x = this.options.x;
+            chartOptions[this.collection][this.index].y = this.options.y;
+
+            labelOptions[this.collection][this.index].x = this.options.x;
+            labelOptions[this.collection][this.index].y = this.options.y;
+        },
+
+        render: function (parent) {
+            var options = this.options,
+                attrs = this.attrsFromOptions(options),
+                style = options.style;
+
+            this.graphic = this.annotation.chart.renderer
+                .label(
+                    '',
+                    0,
+                    -9999, // #10055
+                    options.shape,
+                    null,
+                    null,
+                    options.useHTML,
+                    null,
+                    'annotation-label'
+                )
+                .attr(attrs)
+                .add(parent);
+
+            if (!this.annotation.chart.styledMode) {
+                if (style.color === 'contrast') {
+                    style.color = this.annotation.chart.renderer.getContrast(
+                        ControllableLabel.shapesWithoutBackground.indexOf(
+                            options.shape
+                        ) > -1 ? '#FFFFFF' : options.backgroundColor
+                    );
+                }
+                this.graphic
+                    .css(options.style)
+                    .shadow(options.shadow);
+            }
+
+            if (options.className) {
+                this.graphic.addClass(options.className);
+            }
+
+            this.graphic.labelrank = options.labelrank;
+
+            controllableMixin.render.call(this);
+        },
+
+        redraw: function (animation) {
+            var options = this.options,
+                text = this.text || options.format || options.text,
+                label = this.graphic,
+                point = this.points[0],
+                show = false,
+                anchor,
+                attrs;
+
+            label.attr({
+                text: text ?
+                    H.format(
+                        text,
+                        point.getLabelConfig(),
+                        this.annotation.chart
+                    ) :
+                    options.formatter.call(point, this)
+            });
+
+            anchor = this.anchor(point);
+            attrs = this.position(anchor);
+            show = attrs;
+
+            if (show) {
+                label.alignAttr = attrs;
+
+                attrs.anchorX = anchor.absolutePosition.x;
+                attrs.anchorY = anchor.absolutePosition.y;
+
+                label[animation ? 'animate' : 'attr'](attrs);
+            } else {
+                label.attr({
+                    x: 0,
+                    y: -9999 // #10055
+                });
+            }
+
+            label.placed = Boolean(show);
+
+            controllableMixin.redraw.call(this, animation);
+        },
+        /**
+         * All basic shapes don't support alignTo() method except label.
+         * For a controllable label, we need to subtract translation from
+         * options.
+         */
+        anchor: function () {
+            var anchor = controllableMixin.anchor.apply(this, arguments),
+                x = this.options.x || 0,
+                y = this.options.y || 0;
+
+            anchor.absolutePosition.x -= x;
+            anchor.absolutePosition.y -= y;
+
+            anchor.relativePosition.x -= x;
+            anchor.relativePosition.y -= y;
+
+            return anchor;
+        },
+
+        /**
+         * Returns the label position relative to its anchor.
+         *
+         * @param {Highcharts.AnnotationAnchorObject} anchor
+         *
+         * @return {Highcharts.AnnotationAnchorPositionObject|null} position
+         */
+        position: function (anchor) {
+            var item = this.graphic,
+                chart = this.annotation.chart,
+                point = this.points[0],
+                itemOptions = this.options,
+                anchorAbsolutePosition = anchor.absolutePosition,
+                anchorRelativePosition = anchor.relativePosition,
+                itemPosition,
+                alignTo,
+                itemPosRelativeX,
+                itemPosRelativeY,
+
+                showItem =
+                    point.series.visible &&
+                    MockPoint.prototype.isInsidePane.call(point);
+
+            if (showItem) {
+
+                if (itemOptions.distance) {
+                    itemPosition = H.Tooltip.prototype.getPosition.call(
+                        {
+                            chart: chart,
+                            distance: pick(itemOptions.distance, 16)
+                        },
+                        item.width,
+                        item.height,
+                        {
+                            plotX: anchorRelativePosition.x,
+                            plotY: anchorRelativePosition.y,
+                            negative: point.negative,
+                            ttBelow: point.ttBelow,
+                            h: anchorRelativePosition.height ||
+                            anchorRelativePosition.width
+                        }
+                    );
+                } else if (itemOptions.positioner) {
+                    itemPosition = itemOptions.positioner.call(this);
+                } else {
+                    alignTo = {
+                        x: anchorAbsolutePosition.x,
+                        y: anchorAbsolutePosition.y,
+                        width: 0,
+                        height: 0
+                    };
+
+                    itemPosition = ControllableLabel.alignedPosition(
+                        extend(itemOptions, {
+                            width: item.width,
+                            height: item.height
+                        }),
+                        alignTo
+                    );
+
+                    if (this.options.overflow === 'justify') {
+                        itemPosition = ControllableLabel.alignedPosition(
+                            ControllableLabel.justifiedOptions(
+                                chart,
+                                item,
+                                itemOptions,
+                                itemPosition
+                            ),
+                            alignTo
+                        );
+                    }
+                }
+
+
+                if (itemOptions.crop) {
+                    itemPosRelativeX = itemPosition.x - chart.plotLeft;
+                    itemPosRelativeY = itemPosition.y - chart.plotTop;
+
+                    showItem =
+                        chart.isInsidePlot(
+                            itemPosRelativeX,
+                            itemPosRelativeY
+                        ) &&
+                        chart.isInsidePlot(
+                            itemPosRelativeX + item.width,
+                            itemPosRelativeY + item.height
+                        );
+                }
+            }
+
+            return showItem ? itemPosition : null;
+        }
+    }
+);
+
+/* ********************************************************************** */
+
+/**
+ * General symbol definition for labels with connector
+ * @private
+ */
+H.SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
+    var anchorX = options && options.anchorX,
+        anchorY = options && options.anchorY,
+        path,
+        yOffset,
+        lateral = w / 2;
+
+    if (isNumber(anchorX) && isNumber(anchorY)) {
+
+        path = ['M', anchorX, anchorY];
+
+        // Prefer 45 deg connectors
+        yOffset = y - anchorY;
+        if (yOffset < 0) {
+            yOffset = -h - yOffset;
+        }
+        if (yOffset < w) {
+            lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
+        }
+
+        // Anchor below label
+        if (anchorY > y + h) {
+            path.push('L', x + lateral, y + h);
+
+            // Anchor above label
+        } else if (anchorY < y) {
+            path.push('L', x + lateral, y);
+
+            // Anchor left of label
+        } else if (anchorX < x) {
+            path.push('L', x, y + h / 2);
+
+            // Anchor right of label
+        } else if (anchorX > x + w) {
+            path.push('L', x + w, y + h / 2);
+        }
+    }
+
+    return path || [];
+};
+
+export default ControllableLabel;

Some files were not shown because too many files changed in this diff