diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..f1d46a7 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.boqiangli.com \ No newline at end of file diff --git a/Categories/HPCC-Systems/index.html b/Categories/HPCC-Systems/index.html new file mode 100644 index 0000000..9b03f89 --- /dev/null +++ b/Categories/HPCC-Systems/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Category: HPCC Systems | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+

HPCC Systems + Category +

+
+ + +
+ 2023 +
+ + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Categories/index.html b/Categories/index.html new file mode 100644 index 0000000..b9997fd --- /dev/null +++ b/Categories/index.html @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Categories | Boqiang's Blog + + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ +

Categories +

+ + + +
+ + + +
+
+ 1 category in total +
+ +
+ +
+ + + +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tags/Blog/index.html b/Tags/Blog/index.html new file mode 100644 index 0000000..06534ed --- /dev/null +++ b/Tags/Blog/index.html @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Tag: Blog | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+

Blog + Tag +

+
+ + +
+ 2023 +
+ + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tags/ECL-Programming-Language/index.html b/Tags/ECL-Programming-Language/index.html new file mode 100644 index 0000000..8c99937 --- /dev/null +++ b/Tags/ECL-Programming-Language/index.html @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Tag: ECL Programming Language | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+

ECL Programming Language + Tag +

+
+ + +
+ 2023 +
+ + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tags/ECL/index.html b/Tags/ECL/index.html new file mode 100644 index 0000000..276da6f --- /dev/null +++ b/Tags/ECL/index.html @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Tag: ECL | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+

ECL + Tag +

+
+ + +
+ 2023 +
+ + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tags/HPCC-Systems/index.html b/Tags/HPCC-Systems/index.html new file mode 100644 index 0000000..49832fe --- /dev/null +++ b/Tags/HPCC-Systems/index.html @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Tag: HPCC Systems | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+

HPCC Systems + Tag +

+
+ + +
+ 2023 +
+ + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tags/TensorFlow/index.html b/Tags/TensorFlow/index.html new file mode 100644 index 0000000..136c1c2 --- /dev/null +++ b/Tags/TensorFlow/index.html @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Tag: TensorFlow | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+

TensorFlow + Tag +

+
+ + +
+ 2023 +
+ + + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tags/WSL/index.html b/Tags/WSL/index.html new file mode 100644 index 0000000..23eb793 --- /dev/null +++ b/Tags/WSL/index.html @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Tag: WSL | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+

WSL + Tag +

+
+ + +
+ 2023 +
+ + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tags/index.html b/Tags/index.html new file mode 100644 index 0000000..ea3cfe1 --- /dev/null +++ b/Tags/index.html @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Tags | Boqiang's Blog + + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ +

Tags +

+ + + +
+ + + +
+ +
+ + + +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..d3861ab --- /dev/null +++ b/about/index.html @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +About Me | Boqiang's Blog + + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ +

About Me +

+ + + +
+ + + +
+

In 2018, I graduated from Xidian University with a bachelor's degree +in communication engineering. In the same year, I was admitted to the +University of Science and Technology of China as the first place in +software engineering where I recievied a master's degree in 2021.

+

From July 2019 to June 2021, I was an intern at Intel Beijing engaged +in the decision-making and planning of autonomous vehicles. From July +2021 to August 2022, I worked on lane change planning for self-driving +cars in Didi's autonomous driving department (Voyager).

+

I recieved the following awards:

+
    +
  1. First class award. Sep. 2014
  2. +
  3. Freshman Entrance Scholarship. Sep. 2018
  4. +
  5. Bosch scholarship. Sep. 2019
  6. +
  7. The second prize of the National Postgraduate Mathematical Contest +in Modeling. Nov. 2019
  8. +
  9. Second scholarship. Sep. 2020
  10. +
+

You can reach me via my email .

+ +
+ + + +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/05/index.html b/archives/2023/05/index.html new file mode 100644 index 0000000..33d0c57 --- /dev/null +++ b/archives/2023/05/index.html @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html new file mode 100644 index 0000000..b0ee81b --- /dev/null +++ b/archives/2023/06/index.html @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + + + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/07/index.html b/archives/2023/07/index.html new file mode 100644 index 0000000..a71d41f --- /dev/null +++ b/archives/2023/07/index.html @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + + + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html new file mode 100644 index 0000000..22e7882 --- /dev/null +++ b/archives/2023/08/index.html @@ -0,0 +1,283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 0000000..83914f7 --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/page/2/index.html b/archives/2023/page/2/index.html new file mode 100644 index 0000000..fc9f4c0 --- /dev/null +++ b/archives/2023/page/2/index.html @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 0000000..e4f3164 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 0000000..d3bc80b --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Archive | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+
+
+ Um..! 11 posts in total. Keep on posting. +
+ + +
+ 2023 +
+ + + + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..29475bf --- /dev/null +++ b/css/main.css @@ -0,0 +1,2752 @@ +:root { + --body-bg-color: #f5f7f9; + --content-bg-color: #fff; + --card-bg-color: #f5f5f5; + --text-color: #555; + --blockquote-color: #666; + --link-color: #555; + --link-hover-color: #222; + --brand-color: #fff; + --brand-hover-color: #fff; + --table-row-odd-bg-color: #f9f9f9; + --table-row-hover-bg-color: #f5f5f5; + --menu-item-bg-color: #f5f5f5; + --theme-color: #222; + --btn-default-bg: #fff; + --btn-default-color: #555; + --btn-default-border-color: #555; + --btn-default-hover-bg: #222; + --btn-default-hover-color: #fff; + --btn-default-hover-border-color: #222; + --highlight-background: #f3f3f3; + --highlight-foreground: #444; + --highlight-gutter-background: #e1e1e1; + --highlight-gutter-foreground: #555; + color-scheme: light; +} +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} +body { + margin: 0; +} +main { + display: block; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +a { + background: transparent; +} +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} +b, +strong { + font-weight: bolder; +} +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +img { + border-style: none; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} +button, +input { +/* 1 */ + overflow: visible; +} +button, +select { +/* 1 */ + text-transform: none; +} +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + border-style: none; + padding: 0; +} +button:-moz-focusring, +[type='button']:-moz-focusring, +[type='reset']:-moz-focusring, +[type='submit']:-moz-focusring { + outline: 1px dotted ButtonText; +} +fieldset { + padding: 0.35em 0.75em 0.625em; +} +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} +progress { + vertical-align: baseline; +} +textarea { + overflow: auto; +} +[type='checkbox'], +[type='radio'] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; +} +[type='search'] { + outline-offset: -2px; /* 2 */ + -webkit-appearance: textfield; /* 1 */ +} +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-file-upload-button { + font: inherit; /* 2 */ + -webkit-appearance: button; /* 1 */ +} +details { + display: block; +} +summary { + display: list-item; +} +template { + display: none; +} +[hidden] { + display: none; +} +::selection { + background: #262a30; + color: #eee; +} +html, +body { + height: 100%; +} +body { + background: var(--body-bg-color); + box-sizing: border-box; + color: var(--text-color); + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: 1em; + line-height: 2; + min-height: 100%; + position: relative; + transition: padding 0.2s ease-in-out; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-weight: bold; + line-height: 1.5; + margin: 30px 0 15px; +} +h1 { + font-size: 1.5em; +} +h2 { + font-size: 1.375em; +} +h3 { + font-size: 1.25em; +} +h4 { + font-size: 1.125em; +} +h5 { + font-size: 1em; +} +h6 { + font-size: 0.875em; +} +p { + margin: 0 0 20px; +} +a { + border-bottom: 1px solid #999; + color: var(--link-color); + cursor: pointer; + outline: 0; + text-decoration: none; + overflow-wrap: break-word; +} +a:hover { + border-bottom-color: var(--link-hover-color); + color: var(--link-hover-color); +} +iframe, +img, +video, +embed { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 100%; +} +hr { + background-image: repeating-linear-gradient(-45deg, #ddd, #ddd 4px, transparent 4px, transparent 8px); + border: 0; + height: 3px; + margin: 40px 0; +} +blockquote { + border-left: 4px solid #ddd; + color: var(--blockquote-color); + margin: 0; + padding: 0 15px; +} +blockquote cite::before { + content: '-'; + padding: 0 5px; +} +dt { + font-weight: bold; +} +dd { + margin: 0; + padding: 0; +} +.table-container { + overflow: auto; +} +table { + border-collapse: collapse; + border-spacing: 0; + font-size: 0.875em; + margin: 0 0 20px; + width: 100%; +} +tbody tr:nth-of-type(odd) { + background: var(--table-row-odd-bg-color); +} +tbody tr:hover { + background: var(--table-row-hover-bg-color); +} +caption, +th, +td { + padding: 8px; +} +th, +td { + border: 1px solid #ddd; + border-bottom: 3px solid #ddd; +} +th { + font-weight: 700; + padding-bottom: 10px; +} +td { + border-bottom-width: 1px; +} +.btn { + background: var(--btn-default-bg); + border: 2px solid var(--btn-default-border-color); + border-radius: 2px; + color: var(--btn-default-color); + display: inline-block; + font-size: 0.875em; + line-height: 2; + padding: 0 20px; + transition: background-color 0.2s ease-in-out; +} +.btn:hover { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.btn + .btn { + margin: 0 0 8px 8px; +} +.btn .fa-fw { + text-align: left; + width: 1.285714285714286em; +} +.toggle { + line-height: 0; +} +.toggle .toggle-line { + background: #fff; + display: block; + height: 2px; + left: 0; + position: relative; + top: 0; + transition: all 0.4s; + width: 100%; +} +.toggle .toggle-line:not(:first-child) { + margin-top: 3px; +} +.toggle.toggle-arrow .toggle-line:first-child { + left: 50%; + top: 2px; + transform: rotate(45deg); + width: 50%; +} +.toggle.toggle-arrow .toggle-line:last-child { + left: 50%; + top: -2px; + transform: rotate(-45deg); + width: 50%; +} +.toggle.toggle-close .toggle-line:nth-child(2) { + opacity: 0; +} +.toggle.toggle-close .toggle-line:first-child { + top: 5px; + transform: rotate(45deg); +} +.toggle.toggle-close .toggle-line:last-child { + top: -5px; + transform: rotate(-45deg); +} +/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} +code, +kbd, +figure.highlight, +pre { + background: var(--highlight-background); + color: var(--highlight-foreground); +} +figure.highlight, +pre { + line-height: 1.6; + margin: 0 auto 20px; +} +figure.highlight figcaption, +pre .caption, +pre figcaption { + background: var(--highlight-gutter-background); + color: var(--highlight-foreground); + display: flow-root; + font-size: 0.875em; + line-height: 1.2; + padding: 0.5em; +} +figure.highlight figcaption a, +pre .caption a, +pre figcaption a { + color: var(--highlight-foreground); + float: right; +} +figure.highlight figcaption a:hover, +pre .caption a:hover, +pre figcaption a:hover { + border-bottom-color: var(--highlight-foreground); +} +pre, +code { + font-family: consolas, Menlo, monospace, 'PingFang SC', 'Microsoft YaHei'; +} +code { + border-radius: 3px; + font-size: 0.875em; + padding: 2px 4px; + overflow-wrap: break-word; +} +kbd { + border: 2px solid #ccc; + border-radius: 0.2em; + box-shadow: 0.1em 0.1em 0.2em rgba(0,0,0,0.1); + font-family: inherit; + padding: 0.1em 0.3em; + white-space: nowrap; +} +figure.highlight { + position: relative; +} +figure.highlight pre { + border: 0; + margin: 0; + padding: 10px 0; +} +figure.highlight table { + border: 0; + margin: 0; + width: auto; +} +figure.highlight td { + border: 0; + padding: 0; +} +figure.highlight .gutter { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +figure.highlight .gutter pre { + background: var(--highlight-gutter-background); + color: var(--highlight-gutter-foreground); + padding-left: 10px; + padding-right: 10px; + text-align: right; +} +figure.highlight .code pre { + padding-left: 10px; + width: 100%; +} +figure.highlight .marked { + background: rgba(0,0,0,0.3); +} +pre .caption, +pre figcaption { + margin-bottom: 10px; +} +.gist table { + width: auto; +} +.gist table td { + border: 0; +} +pre { + overflow: auto; + padding: 10px; + position: relative; +} +pre code { + background: none; + padding: 0; + text-shadow: none; +} +.blockquote-center { + border-left: 0; + margin: 40px 0; + padding: 0; + position: relative; + text-align: center; +} +.blockquote-center::before, +.blockquote-center::after { + left: 0; + line-height: 1; + opacity: 0.6; + position: absolute; + width: 100%; +} +.blockquote-center::before { + border-top: 1px solid #ccc; + text-align: left; + top: -20px; + content: '\f10d'; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.blockquote-center::after { + border-bottom: 1px solid #ccc; + bottom: -20px; + text-align: right; + content: '\f10e'; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.blockquote-center p, +.blockquote-center div { + text-align: center; +} +.group-picture { + margin-bottom: 20px; +} +.group-picture .group-picture-row { + display: flex; + gap: 3px; + margin-bottom: 3px; +} +.group-picture .group-picture-column { + flex: 1; +} +.group-picture .group-picture-column img { + height: 100%; + margin: 0; + object-fit: cover; + width: 100%; +} +.post-body .label { + color: #555; + padding: 0 2px; +} +.post-body .label.default { + background: #f0f0f0; +} +.post-body .label.primary { + background: #efe6f7; +} +.post-body .label.info { + background: #e5f2f8; +} +.post-body .label.success { + background: #e7f4e9; +} +.post-body .label.warning { + background: #fcf6e1; +} +.post-body .label.danger { + background: #fae8eb; +} +.post-body .link-grid { + display: grid; + grid-gap: 1.5rem; + gap: 1.5rem; + grid-template-columns: 1fr 1fr; + margin-bottom: 20px; + padding: 1rem; +} +@media (max-width: 767px) { + .post-body .link-grid { + grid-template-columns: 1fr; + } +} +.post-body .link-grid .link-grid-container { + border: solid #ddd; + box-shadow: 1rem 1rem 0.5rem rgba(0,0,0,0.5); + min-height: 5rem; + min-width: 0; + padding: 0.5rem; + position: relative; + transition: background 0.3s; +} +.post-body .link-grid .link-grid-container:hover { + animation: next-shake 0.5s; + background: var(--card-bg-color); +} +.post-body .link-grid .link-grid-container:active { + box-shadow: 0.5rem 0.5rem 0.25rem rgba(0,0,0,0.5); + transform: translate(0.2rem, 0.2rem); +} +.post-body .link-grid .link-grid-container .link-grid-image { + border: 1px solid #ddd; + border-radius: 50%; + box-sizing: border-box; + height: 5rem; + padding: 3px; + position: absolute; + width: 5rem; +} +.post-body .link-grid .link-grid-container p { + margin: 0 1rem 0 6rem; +} +.post-body .link-grid .link-grid-container p:first-of-type { + font-size: 1.2em; +} +.post-body .link-grid .link-grid-container p:last-of-type { + font-size: 0.8em; + line-height: 1.3rem; + opacity: 0.7; +} +.post-body .link-grid .link-grid-container a { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +@-moz-keyframes next-shake { + 0% { + transform: translate(1pt, 1pt) rotate(0deg); + } + 10% { + transform: translate(-1pt, -2pt) rotate(-1deg); + } + 20% { + transform: translate(-3pt, 0pt) rotate(1deg); + } + 30% { + transform: translate(3pt, 2pt) rotate(0deg); + } + 40% { + transform: translate(1pt, -1pt) rotate(1deg); + } + 50% { + transform: translate(-1pt, 2pt) rotate(-1deg); + } + 60% { + transform: translate(-3pt, 1pt) rotate(0deg); + } + 70% { + transform: translate(3pt, 1pt) rotate(-1deg); + } + 80% { + transform: translate(-1pt, -1pt) rotate(1deg); + } + 90% { + transform: translate(1pt, 2pt) rotate(0deg); + } + 100% { + transform: translate(1pt, -2pt) rotate(-1deg); + } +} +@-webkit-keyframes next-shake { + 0% { + transform: translate(1pt, 1pt) rotate(0deg); + } + 10% { + transform: translate(-1pt, -2pt) rotate(-1deg); + } + 20% { + transform: translate(-3pt, 0pt) rotate(1deg); + } + 30% { + transform: translate(3pt, 2pt) rotate(0deg); + } + 40% { + transform: translate(1pt, -1pt) rotate(1deg); + } + 50% { + transform: translate(-1pt, 2pt) rotate(-1deg); + } + 60% { + transform: translate(-3pt, 1pt) rotate(0deg); + } + 70% { + transform: translate(3pt, 1pt) rotate(-1deg); + } + 80% { + transform: translate(-1pt, -1pt) rotate(1deg); + } + 90% { + transform: translate(1pt, 2pt) rotate(0deg); + } + 100% { + transform: translate(1pt, -2pt) rotate(-1deg); + } +} +@-o-keyframes next-shake { + 0% { + transform: translate(1pt, 1pt) rotate(0deg); + } + 10% { + transform: translate(-1pt, -2pt) rotate(-1deg); + } + 20% { + transform: translate(-3pt, 0pt) rotate(1deg); + } + 30% { + transform: translate(3pt, 2pt) rotate(0deg); + } + 40% { + transform: translate(1pt, -1pt) rotate(1deg); + } + 50% { + transform: translate(-1pt, 2pt) rotate(-1deg); + } + 60% { + transform: translate(-3pt, 1pt) rotate(0deg); + } + 70% { + transform: translate(3pt, 1pt) rotate(-1deg); + } + 80% { + transform: translate(-1pt, -1pt) rotate(1deg); + } + 90% { + transform: translate(1pt, 2pt) rotate(0deg); + } + 100% { + transform: translate(1pt, -2pt) rotate(-1deg); + } +} +@keyframes next-shake { + 0% { + transform: translate(1pt, 1pt) rotate(0deg); + } + 10% { + transform: translate(-1pt, -2pt) rotate(-1deg); + } + 20% { + transform: translate(-3pt, 0pt) rotate(1deg); + } + 30% { + transform: translate(3pt, 2pt) rotate(0deg); + } + 40% { + transform: translate(1pt, -1pt) rotate(1deg); + } + 50% { + transform: translate(-1pt, 2pt) rotate(-1deg); + } + 60% { + transform: translate(-3pt, 1pt) rotate(0deg); + } + 70% { + transform: translate(3pt, 1pt) rotate(-1deg); + } + 80% { + transform: translate(-1pt, -1pt) rotate(1deg); + } + 90% { + transform: translate(1pt, 2pt) rotate(0deg); + } + 100% { + transform: translate(1pt, -2pt) rotate(-1deg); + } +} +.post-body .note { + border-radius: 3px; + margin-bottom: 20px; + padding: 1em; + position: relative; + border: 1px solid #eee; + border-left-width: 5px; +} +.post-body .note summary { + cursor: pointer; + outline: 0; +} +.post-body .note summary p { + display: inline; +} +.post-body .note h2, +.post-body .note h3, +.post-body .note h4, +.post-body .note h5, +.post-body .note h6 { + border-bottom: initial; + margin: 0; + padding-top: 0; +} +.post-body .note p:first-child, +.post-body .note ul:first-child, +.post-body .note ol:first-child, +.post-body .note table:first-child, +.post-body .note pre:first-child, +.post-body .note blockquote:first-child, +.post-body .note img:first-child { + margin-top: 0; +} +.post-body .note p:last-child, +.post-body .note ul:last-child, +.post-body .note ol:last-child, +.post-body .note table:last-child, +.post-body .note pre:last-child, +.post-body .note blockquote:last-child, +.post-body .note img:last-child { + margin-bottom: 0; +} +.post-body .note.default { + border-left-color: #777; +} +.post-body .note.default h2, +.post-body .note.default h3, +.post-body .note.default h4, +.post-body .note.default h5, +.post-body .note.default h6 { + color: #777; +} +.post-body .note.primary { + border-left-color: #6f42c1; +} +.post-body .note.primary h2, +.post-body .note.primary h3, +.post-body .note.primary h4, +.post-body .note.primary h5, +.post-body .note.primary h6 { + color: #6f42c1; +} +.post-body .note.info { + border-left-color: #428bca; +} +.post-body .note.info h2, +.post-body .note.info h3, +.post-body .note.info h4, +.post-body .note.info h5, +.post-body .note.info h6 { + color: #428bca; +} +.post-body .note.success { + border-left-color: #5cb85c; +} +.post-body .note.success h2, +.post-body .note.success h3, +.post-body .note.success h4, +.post-body .note.success h5, +.post-body .note.success h6 { + color: #5cb85c; +} +.post-body .note.warning { + border-left-color: #f0ad4e; +} +.post-body .note.warning h2, +.post-body .note.warning h3, +.post-body .note.warning h4, +.post-body .note.warning h5, +.post-body .note.warning h6 { + color: #f0ad4e; +} +.post-body .note.danger { + border-left-color: #d9534f; +} +.post-body .note.danger h2, +.post-body .note.danger h3, +.post-body .note.danger h4, +.post-body .note.danger h5, +.post-body .note.danger h6 { + color: #d9534f; +} +.post-body .tabs { + margin-bottom: 20px; +} +.post-body .tabs, +.tabs-comment { + padding-top: 10px; +} +.post-body .tabs ul.nav-tabs, +.tabs-comment ul.nav-tabs { + background: var(--content-bg-color); + display: flex; + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: 0; + padding: 0; + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 5; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs, + .tabs-comment ul.nav-tabs { + display: block; + margin-bottom: 5px; + } +} +.post-body .tabs ul.nav-tabs li.tab, +.tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid #ddd; + border-left: 1px solid transparent; + border-right: 1px solid transparent; + border-radius: 0 0 0 0; + border-top: 3px solid transparent; + flex-grow: 1; + list-style-type: none; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid transparent; + border-left: 3px solid transparent; + border-right: 1px solid transparent; + border-top: 1px solid transparent; + } +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-radius: 0; + } +} +.post-body .tabs ul.nav-tabs li.tab a, +.tabs-comment ul.nav-tabs li.tab a { + border-bottom: initial; + display: block; + line-height: 1.8; + padding: 0.25em 0.75em; + text-align: center; + transition: all 0.2s ease-out; +} +.post-body .tabs ul.nav-tabs li.tab a i, +.tabs-comment ul.nav-tabs li.tab a i { + width: 1.285714285714286em; +} +.post-body .tabs ul.nav-tabs li.tab.active, +.tabs-comment ul.nav-tabs li.tab.active { + border-bottom-color: transparent; + border-left-color: #ddd; + border-right-color: #ddd; + border-top-color: #fc6423; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab.active, + .tabs-comment ul.nav-tabs li.tab.active { + border-bottom-color: #ddd; + border-left-color: #fc6423; + border-right-color: #ddd; + border-top-color: #ddd; + } +} +.post-body .tabs ul.nav-tabs li.tab.active a, +.tabs-comment ul.nav-tabs li.tab.active a { + cursor: default; +} +.post-body .tabs .tab-content, +.tabs-comment .tab-content { + border: 1px solid #ddd; + border-radius: 0 0 0 0; + border-top-color: transparent; +} +@media (max-width: 413px) { + .post-body .tabs .tab-content, + .tabs-comment .tab-content { + border-radius: 0; + border-top-color: #ddd; + } +} +.post-body .tabs .tab-content .tab-pane, +.tabs-comment .tab-content .tab-pane { + padding: 20px 20px 0; +} +.post-body .tabs .tab-content .tab-pane:not(.active), +.tabs-comment .tab-content .tab-pane:not(.active) { + display: none; +} +.pagination .prev, +.pagination .next, +.pagination .page-number, +.pagination .space { + display: inline-block; + margin: -1px 10px 0; + padding: 0 10px; +} +@media (max-width: 767px) { + .pagination .prev, + .pagination .next, + .pagination .page-number, + .pagination .space { + margin: 0 5px; + } +} +.pagination .page-number.current, +.algolia-pagination .current .page-number { + background: #ccc; + border-color: #ccc; + color: var(--content-bg-color); +} +.pagination { + border-top: 1px solid #eee; + margin: 120px 0 0; + text-align: center; +} +.pagination .prev, +.pagination .next, +.pagination .page-number { + border-bottom: 0; + border-top: 1px solid #eee; + transition: border-color 0.2s ease-in-out; +} +.pagination .prev:hover, +.pagination .next:hover, +.pagination .page-number:hover { + border-top-color: var(--link-hover-color); +} +@media (max-width: 767px) { + .pagination { + border-top: 0; + } + .pagination .prev, + .pagination .next, + .pagination .page-number { + border-bottom: 1px solid #eee; + border-top: 0; + } + .pagination .prev:hover, + .pagination .next:hover, + .pagination .page-number:hover { + border-bottom-color: var(--link-hover-color); + } +} +.pagination .space { + margin: 0; + padding: 0; +} +.comments { + margin-top: 60px; + overflow: hidden; +} +.comment-button-group { + display: flex; + display: flex; + flex-wrap: wrap; + justify-content: center; + justify-content: center; + margin: 1em 0; +} +.comment-button-group .comment-button { + margin: 0.1em 0.2em; +} +.comment-button-group .comment-button.active { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.comment-position { + display: none; +} +.comment-position.active { + display: block; +} +.tabs-comment { + margin-top: 4em; + padding-top: 0; +} +.tabs-comment .comments { + margin-top: 0; + padding-top: 0; +} +.headband { + background: var(--theme-color); + height: 3px; +} +@media (max-width: 991px) { + .headband { + display: none; + } +} +header.header { + background: transparent; +} +.header-inner { + margin: 0 auto; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .header-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .header-inner { + width: 73%; + } +} +.site-brand-container { + display: flex; + flex-shrink: 0; + padding: 0 10px; +} +.use-motion header.header, +.use-motion .site-brand-container .toggle { + opacity: 0; +} +.site-meta { + flex-grow: 1; + text-align: center; +} +@media (max-width: 767px) { + .site-meta { + text-align: center; + } +} +.custom-logo-image { + margin-top: 20px; +} +@media (max-width: 991px) { + .custom-logo-image { + display: none; + } +} +.brand { + border-bottom: 0; + color: var(--brand-color); + display: inline-block; + padding: 0 40px; +} +.brand:hover { + color: var(--brand-hover-color); +} +.site-title { + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: 1.375em; + font-weight: normal; + line-height: 1.5; + margin: 0; +} +.site-subtitle { + color: #ddd; + font-size: 0.8125em; + margin: 10px 0; +} +.use-motion .site-title, +.use-motion .site-subtitle, +.use-motion .custom-logo-image { + opacity: 0; + position: relative; + top: -10px; +} +.site-nav-toggle, +.site-nav-right { + display: none; +} +@media (max-width: 767px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: var(--text-color); + padding: 10px; + width: 22px; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: var(--text-color); + border-radius: 1px; +} +@media (max-width: 767px) { + .site-nav { + --scroll-height: 0; + height: 0; + overflow: hidden; + transition: height 0.2s ease-in-out; + } + body:not(.site-nav-on) .site-nav .animated { + animation: none; + } + body.site-nav-on .site-nav { + height: var(--scroll-height); + } +} +.menu { + margin: 0; + padding: 1em 0; + text-align: center; +} +.menu-item { + display: inline-block; + list-style: none; + margin: 0 10px; +} +@media (max-width: 767px) { + .menu-item { + display: block; + margin-top: 10px; + } + .menu-item.menu-item-search { + display: none; + } +} +.menu-item a { + border-bottom: 0; + display: block; + font-size: 0.8125em; + transition: border-color 0.2s ease-in-out; +} +.menu-item a:hover, +.menu-item a.menu-item-active { + background: var(--menu-item-bg-color); +} +.menu-item .fa, +.menu-item .fab, +.menu-item .far, +.menu-item .fas { + margin-right: 8px; +} +.menu-item .badge { + display: inline-block; + font-weight: bold; + line-height: 1; + margin-left: 0.35em; + margin-top: 0.35em; + text-align: center; + white-space: nowrap; +} +@media (max-width: 767px) { + .menu-item .badge { + float: right; + margin-left: 0; + } +} +.use-motion .menu-item { + visibility: hidden; +} +.sidebar-inner { + color: #999; + max-height: calc(100vh - 24px); + padding: 18px 10px; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; +} +.site-overview-item:not(:first-child) { + margin-top: 10px; +} +.cc-license .cc-opacity { + border-bottom: 0; + opacity: 0.7; +} +.cc-license .cc-opacity:hover { + opacity: 0.9; +} +.cc-license img { + display: inline-block; +} +.site-author-image { + border: 1px solid #eee; + max-width: 120px; + padding: 2px; +} +.site-author-name { + color: var(--text-color); + font-weight: 600; + margin: 0; +} +.site-description { + color: #999; + font-size: 0.8125em; + margin-top: 0; +} +.links-of-author a { + font-size: 0.8125em; +} +.links-of-author .fa, +.links-of-author .fab, +.links-of-author .far, +.links-of-author .fas { + margin-right: 2px; +} +.sidebar .sidebar-button:not(:first-child) { + margin-top: 15px; +} +.sidebar .sidebar-button button { + background: transparent; + color: #fc6423; + cursor: pointer; + line-height: 2; + padding: 0 15px; + border: 1px solid #fc6423; + border-radius: 4px; +} +.sidebar .sidebar-button button:hover { + background: #fc6423; + color: #fff; +} +.sidebar .sidebar-button button .fa, +.sidebar .sidebar-button button .fab, +.sidebar .sidebar-button button .far, +.sidebar .sidebar-button button .fas { + margin-right: 5px; +} +.links-of-blogroll { + font-size: 0.8125em; +} +.links-of-blogroll-title { + font-size: 0.875em; + font-weight: 600; + margin-top: 0; +} +.links-of-blogroll-list { + list-style: none; + margin: 0; + padding: 0; +} +.sidebar-dimmer { + display: none; +} +@media (max-width: 991px) { + .sidebar-dimmer { + background: #000; + display: block; + height: 100%; + left: 0; + opacity: 0; + position: fixed; + top: 0; + transition: visibility 0.4s, opacity 0.4s; + visibility: hidden; + width: 100%; + z-index: 10; + } + .sidebar-active .sidebar-dimmer { + opacity: 0.7; + visibility: visible; + } +} +.sidebar-nav { + display: none; + margin: 0; + padding-bottom: 20px; + padding-left: 0; +} +.sidebar-nav-active .sidebar-nav { + display: block; +} +.sidebar-nav li { + border-bottom: 1px solid transparent; + color: var(--text-color); + cursor: pointer; + display: inline-block; + font-size: 0.875em; +} +.sidebar-nav li.sidebar-nav-overview { + margin-left: 10px; +} +.sidebar-nav li:hover { + color: #fc6423; +} +.sidebar-toc-active .sidebar-nav-toc, +.sidebar-overview-active .sidebar-nav-overview { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sidebar-toc-active .sidebar-nav-toc:hover, +.sidebar-overview-active .sidebar-nav-overview:hover { + color: #fc6423; +} +.sidebar-panel-container { + flex: 1; + overflow-x: hidden; + overflow-y: auto; +} +.sidebar-panel { + display: none; +} +.sidebar-overview-active .site-overview-wrap { + display: flex; + flex-direction: column; + justify-content: center; +} +.sidebar-toc-active .post-toc-wrap { + display: block; +} +.sidebar-toggle { + bottom: 45px; + height: 12px; + padding: 6px 5px; + width: 14px; + background: #222; + cursor: pointer; + opacity: 0.6; + position: fixed; + z-index: 30; + right: 30px; +} +@media (max-width: 991px) { + .sidebar-toggle { + right: 20px; + } +} +.sidebar-toggle:hover { + opacity: 0.8; +} +@media (max-width: 991px) { + .sidebar-toggle { + opacity: 0.8; + } +} +.sidebar-toggle:hover .toggle-line { + background: #fc6423; +} +@media (any-hover: hover) { + body:not(.sidebar-active) .sidebar-toggle:hover .toggle-line:first-child { + left: 50%; + top: 2px; + transform: rotate(45deg); + width: 50%; + } + body:not(.sidebar-active) .sidebar-toggle:hover .toggle-line:last-child { + left: 50%; + top: -2px; + transform: rotate(-45deg); + width: 50%; + } +} +.sidebar-active .sidebar-toggle .toggle-line:nth-child(2) { + opacity: 0; +} +.sidebar-active .sidebar-toggle .toggle-line:first-child { + top: 5px; + transform: rotate(45deg); +} +.sidebar-active .sidebar-toggle .toggle-line:last-child { + top: -5px; + transform: rotate(-45deg); +} +.post-toc { + font-size: 0.875em; +} +.post-toc ol { + list-style: none; + margin: 0; + padding: 0 2px 5px 10px; + text-align: left; +} +.post-toc ol > ol { + padding-left: 0; +} +.post-toc ol a { + transition: all 0.2s ease-in-out; +} +.post-toc .nav-item { + line-height: 1.8; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.post-toc .nav .nav-child { + display: none; +} +.post-toc .nav .active > .nav-child { + display: block; +} +.post-toc .nav .active-current > .nav-child { + display: block; +} +.post-toc .nav .active-current > .nav-child > .nav-item { + display: block; +} +.post-toc .nav .active > a { + border-bottom-color: #fc6423; + color: #fc6423; +} +.post-toc .nav .active-current > a { + color: #fc6423; +} +.post-toc .nav .active-current > a:hover { + color: #fc6423; +} +.site-state { + display: flex; + flex-wrap: wrap; + justify-content: center; + line-height: 1.4; +} +.site-state-item { + padding: 0 15px; +} +.site-state-item a { + border-bottom: 0; + display: block; +} +.site-state-item-count { + display: block; + font-size: 1em; + font-weight: 600; +} +.site-state-item-name { + color: #999; + font-size: 0.8125em; +} +.footer { + color: #999; + font-size: 0.875em; + padding: 20px 0; +} +.footer.footer-fixed { + bottom: 0; + left: 0; + position: absolute; + right: 0; +} +.footer-inner { + box-sizing: border-box; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + margin: 0 auto; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .footer-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .footer-inner { + width: 73%; + } +} +.use-motion .footer { + opacity: 0; +} +.languages { + display: inline-block; + font-size: 1.125em; + position: relative; +} +.languages .lang-select-label span { + margin: 0 0.5em; +} +.languages .lang-select { + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + width: 100%; +} +.with-love { + color: #f00; + display: inline-block; + margin: 0 5px; +} +@-moz-keyframes icon-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@-webkit-keyframes icon-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@-o-keyframes icon-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@keyframes icon-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +.noscript-warning { + background-color: #f55; + color: #fff; + font-family: sans-serif; + font-size: 1rem; + font-weight: bold; + left: 0; + position: fixed; + text-align: center; + top: 0; + width: 100%; + z-index: 50; +} +.back-to-top { + font-size: 12px; + bottom: -100px; + box-sizing: border-box; + color: #fff; + padding: 0 6px; + transition: bottom 0.2s ease-in-out; + background: #222; + cursor: pointer; + opacity: 0.6; + position: fixed; + z-index: 30; + right: 30px; + width: 24px; +} +.back-to-top span { + display: none; +} +@media (max-width: 991px) { + .back-to-top { + right: 20px; + } +} +.back-to-top:hover { + opacity: 0.8; +} +@media (max-width: 991px) { + .back-to-top { + opacity: 0.8; + } +} +.back-to-top:hover { + color: #fc6423; +} +.back-to-top.back-to-top-on { + bottom: 30px; +} +.rtl.post-body p, +.rtl.post-body a, +.rtl.post-body h1, +.rtl.post-body h2, +.rtl.post-body h3, +.rtl.post-body h4, +.rtl.post-body h5, +.rtl.post-body h6, +.rtl.post-body li, +.rtl.post-body ul, +.rtl.post-body ol { + direction: rtl; + font-family: UKIJ Ekran; +} +.rtl.post-title { + font-family: UKIJ Ekran; +} +.post-button { + margin-top: 40px; + text-align: center; +} +.use-motion .post-block, +.use-motion .pagination, +.use-motion .comments { + visibility: hidden; +} +.posts-collapse .post-content { + margin-bottom: 35px; + margin-left: 35px; + position: relative; +} +@media (max-width: 767px) { + .posts-collapse .post-content { + margin-left: 0; + margin-right: 0; + } +} +.posts-collapse .post-content .collection-title { + display: none; + font-size: 1.125em; + position: relative; +} +.posts-collapse .post-content .collection-title::before { + background: #999; + border: 1px solid #fff; + margin-left: -6px; + margin-top: -4px; + position: absolute; + top: 50%; + border-radius: 50%; + content: ' '; + height: 10px; + width: 10px; +} +.posts-collapse .post-content .collection-year { + display: none; + font-size: 1.5em; + font-weight: bold; + margin: 60px 0; + position: relative; +} +.posts-collapse .post-content .collection-year::before { + background: #bbb; + margin-left: -4px; + margin-top: -4px; + position: absolute; + top: 50%; + border-radius: 50%; + content: ' '; + height: 8px; + width: 8px; +} +.posts-collapse .post-content .collection-header { + display: block; + margin-left: 20px; +} +.posts-collapse .post-content .collection-header small { + color: #bbb; + margin-left: 5px; +} +.posts-collapse .post-content .post-header { + border-bottom: 1px dashed #ccc; + margin: 30px 2px 0; + padding-left: 15px; + position: relative; + transition: border 0.2s ease-in-out; +} +.posts-collapse .post-content .post-header::before { + background: #bbb; + border: 1px solid #fff; + left: -6px; + position: absolute; + top: 0.75em; + transition: background 0.2s ease-in-out; + border-radius: 50%; + content: ' '; + height: 6px; + width: 6px; +} +.posts-collapse .post-content .post-header:hover { + border-bottom-color: #666; +} +.posts-collapse .post-content .post-header:hover::before { + background: #222; +} +.posts-collapse .post-content .post-meta-container { + display: inline; + font-size: 0.75em; + margin-right: 10px; +} +.posts-collapse .post-content .post-title { + display: inline; +} +.posts-collapse .post-content .post-title a { + border-bottom: 0; + color: var(--link-color); +} +.posts-collapse .post-content .post-title .fa-external-link-alt { + font-size: 0.875em; + margin-left: 5px; +} +.posts-collapse .post-content::before { + background: #f5f5f5; + content: ' '; + height: 100%; + margin-left: -2px; + position: absolute; + top: 1.25em; + width: 4px; +} +.post-body { + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + overflow-wrap: break-word; +} +@media (min-width: 1200px) { + .post-body { + font-size: 1.125em; + } +} +@media (min-width: 992px) { + .post-body { + text-align: justify; + } +} +@media (max-width: 991px) { + .post-body { + text-align: justify; + } +} +.post-body h1 .header-anchor, +.post-body h2 .header-anchor, +.post-body h3 .header-anchor, +.post-body h4 .header-anchor, +.post-body h5 .header-anchor, +.post-body h6 .header-anchor, +.post-body h1 .headerlink, +.post-body h2 .headerlink, +.post-body h3 .headerlink, +.post-body h4 .headerlink, +.post-body h5 .headerlink, +.post-body h6 .headerlink { + border-bottom-style: none; + color: inherit; + float: right; + font-size: 0.875em; + margin-left: 10px; + opacity: 0; +} +.post-body h1 .header-anchor::before, +.post-body h2 .header-anchor::before, +.post-body h3 .header-anchor::before, +.post-body h4 .header-anchor::before, +.post-body h5 .header-anchor::before, +.post-body h6 .header-anchor::before, +.post-body h1 .headerlink::before, +.post-body h2 .headerlink::before, +.post-body h3 .headerlink::before, +.post-body h4 .headerlink::before, +.post-body h5 .headerlink::before, +.post-body h6 .headerlink::before { + content: '\f0c1'; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.post-body h1:hover .header-anchor, +.post-body h2:hover .header-anchor, +.post-body h3:hover .header-anchor, +.post-body h4:hover .header-anchor, +.post-body h5:hover .header-anchor, +.post-body h6:hover .header-anchor, +.post-body h1:hover .headerlink, +.post-body h2:hover .headerlink, +.post-body h3:hover .headerlink, +.post-body h4:hover .headerlink, +.post-body h5:hover .headerlink, +.post-body h6:hover .headerlink { + opacity: 0.5; +} +.post-body h1:hover .header-anchor:hover, +.post-body h2:hover .header-anchor:hover, +.post-body h3:hover .header-anchor:hover, +.post-body h4:hover .header-anchor:hover, +.post-body h5:hover .header-anchor:hover, +.post-body h6:hover .header-anchor:hover, +.post-body h1:hover .headerlink:hover, +.post-body h2:hover .headerlink:hover, +.post-body h3:hover .headerlink:hover, +.post-body h4:hover .headerlink:hover, +.post-body h5:hover .headerlink:hover, +.post-body h6:hover .headerlink:hover { + opacity: 1; +} +.post-body .exturl .fa { + font-size: 0.875em; + margin-left: 4px; +} +.post-body .image-caption, +.post-body img + figcaption, +.post-body .fancybox + figcaption { + color: #999; + font-size: 0.875em; + font-weight: bold; + line-height: 1; + margin: -15px auto 15px; + text-align: center; +} +.post-body iframe, +.post-body img, +.post-body video, +.post-body embed { + margin-bottom: 20px; +} +.post-body .video-container { + height: 0; + margin-bottom: 20px; + overflow: hidden; + padding-top: 75%; + position: relative; + width: 100%; +} +.post-body .video-container iframe, +.post-body .video-container object, +.post-body .video-container embed { + height: 100%; + left: 0; + margin: 0; + position: absolute; + top: 0; + width: 100%; +} +.post-body p a { + color: #0593d3; + border-bottom: none; + border-bottom: 1px solid #0593d3; +} +.post-body p a:hover { + color: #fc6423; + border-bottom: none; + border-bottom: 1px solid #fc6423; +} +.post-gallery { + display: flex; + min-height: 200px; +} +.post-gallery .post-gallery-image { + flex: 1; +} +.post-gallery .post-gallery-image:not(:first-child) { + clip-path: polygon(40px 0, 100% 0, 100% 100%, 0 100%); + margin-left: -20px; +} +.post-gallery .post-gallery-image:not(:last-child) { + margin-right: -20px; +} +.post-gallery .post-gallery-image img { + height: 100%; + object-fit: cover; + opacity: 1; + width: 100%; +} +.posts-expand .post-gallery { + margin-bottom: 60px; +} +.posts-collapse .post-gallery { + margin: 15px 0; +} +.posts-expand .post-header { + font-size: 1.125em; + margin-bottom: 60px; + text-align: center; +} +.posts-expand .post-title { + font-size: 1.5em; + font-weight: normal; + margin: initial; + overflow-wrap: break-word; +} +.posts-expand .post-title-link { + border-bottom: 0; + color: var(--link-color); + display: inline-block; + position: relative; +} +.posts-expand .post-title-link::before { + background: var(--link-color); + bottom: 0; + content: ''; + height: 2px; + left: 0; + position: absolute; + transform: scaleX(0); + transition: transform 0.2s ease-in-out; + width: 100%; +} +.posts-expand .post-title-link:hover::before { + transform: scaleX(1); +} +.posts-expand .post-title-link .fa-external-link-alt { + font-size: 0.875em; + margin-left: 5px; +} +.post-sticky-flag { + display: inline-block; + margin-right: 8px; + transform: rotate(30deg); +} +.posts-expand .post-meta-container { + color: #999; + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: 0.75em; + margin-top: 3px; +} +.posts-expand .post-meta-container .post-description { + font-size: 0.875em; + margin-top: 2px; +} +.posts-expand .post-meta-container time { + border-bottom: 1px dashed #999; +} +.post-meta { + display: flex; + flex-wrap: wrap; + justify-content: center; +} +:not(.post-meta-break) + .post-meta-item::before { + content: '|'; + margin: 0 0.5em; +} +.post-meta-item-icon { + margin-right: 3px; +} +@media (max-width: 991px) { + .post-meta-item-text { + display: none; + } +} +.post-meta-break { + flex-basis: 100%; + height: 0; +} +.post-nav { + border-top: 1px solid #eee; + display: flex; + gap: 30px; + justify-content: space-between; + margin-top: 1em; + padding: 10px 5px 0; +} +.post-nav-item { + flex: 1; +} +.post-nav-item a { + border-bottom: 0; + display: block; + font-size: 0.875em; + line-height: 1.6; +} +.post-nav-item a:active { + top: 2px; +} +.post-nav-item .fa { + font-size: 0.75em; +} +.post-nav-item:first-child .fa { + margin-right: 5px; +} +.post-nav-item:last-child { + text-align: right; +} +.post-nav-item:last-child .fa { + margin-left: 5px; +} +.post-footer { + display: flex; + flex-direction: column; + justify-content: center; +} +.post-eof { + background: #ccc; + height: 1px; + margin: 80px auto 60px; + width: 8%; +} +.post-block:last-of-type .post-eof { + display: none; +} +.post-tags { + margin-top: 40px; + text-align: center; +} +.post-tags a { + display: inline-block; + font-size: 0.8125em; +} +.post-tags a:not(:last-child) { + margin-right: 10px; +} +.post-widgets { + border-top: 1px solid #eee; + margin-top: 15px; + text-align: center; +} +.wpac-rating-container { + height: 20px; + line-height: 20px; + margin-top: 10px; + padding-top: 6px; + text-align: center; +} +.social-like { + display: flex; + font-size: 0.875em; + justify-content: center; + text-align: center; +} +.reward-container { + margin: 1em 0 0; + padding: 1em 0; + text-align: center; +} +.reward-container button { + background: transparent; + color: #fc6423; + cursor: pointer; + line-height: 2; + padding: 0 15px; + border: 2px solid #fc6423; + border-radius: 2px; + outline: 0; + transition: all 0.2s ease-in-out; + vertical-align: text-top; +} +.reward-container button:hover { + background: #fc6423; + color: #fff; +} +.post-reward { + display: none; + padding-top: 20px; +} +.post-reward.active { + display: block; +} +.post-reward div { + display: inline-block; +} +.post-reward div span { + display: block; +} +.post-reward img { + display: inline-block; + margin: 0.8em 2em 0; + max-width: 100%; + width: 180px; +} +@-moz-keyframes next-roll { + from { + transform: rotateZ(30deg); + } + to { + transform: rotateZ(-30deg); + } +} +@-webkit-keyframes next-roll { + from { + transform: rotateZ(30deg); + } + to { + transform: rotateZ(-30deg); + } +} +@-o-keyframes next-roll { + from { + transform: rotateZ(30deg); + } + to { + transform: rotateZ(-30deg); + } +} +@keyframes next-roll { + from { + transform: rotateZ(30deg); + } + to { + transform: rotateZ(-30deg); + } +} +.category-all-page .category-all-title { + text-align: center; +} +.category-all-page .category-all { + margin-top: 20px; +} +.category-all-page .category-list { + list-style: none; + margin: 0; + padding: 0; +} +.category-all-page .category-list-item { + margin: 5px 10px; +} +.category-all-page .category-list-count { + color: #bbb; +} +.category-all-page .category-list-count::before { + content: ' ('; +} +.category-all-page .category-list-count::after { + content: ') '; +} +.category-all-page .category-list-child { + padding-left: 10px; +} +.event-list hr { + background: #222; + margin: 20px 0 45px; +} +.event-list hr::after { + background: #222; + color: #fff; + content: 'NOW'; + display: inline-block; + font-weight: bold; + padding: 0 5px; +} +.event-list .event { + --event-background: #222; + --event-foreground: #bbb; + --event-title: #fff; + background: var(--event-background); + padding: 15px; +} +.event-list .event .event-summary { + border-bottom: 0; + color: var(--event-title); + margin: 0; + padding: 0 0 0 35px; + position: relative; +} +.event-list .event .event-summary::before { + animation: dot-flash 1s alternate infinite ease-in-out; + background: var(--event-title); + left: 0; + margin-top: -6px; + position: absolute; + top: 50%; + border-radius: 50%; + content: ' '; + height: 12px; + width: 12px; +} +.event-list .event:nth-of-type(odd) .event-summary::before { + animation-delay: 0.5s; +} +.event-list .event:not(:last-child) { + margin-bottom: 20px; +} +.event-list .event .event-relative-time { + color: var(--event-foreground); + display: inline-block; + font-size: 12px; + font-weight: normal; + padding-left: 12px; +} +.event-list .event .event-details { + color: var(--event-foreground); + display: block; + line-height: 18px; + padding: 6px 0 6px 35px; +} +.event-list .event .event-details::before { + color: var(--event-foreground); + display: inline-block; + margin-right: 9px; + width: 14px; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.event-list .event .event-details.event-location::before { + content: '\f041'; +} +.event-list .event .event-details.event-duration::before { + content: '\f017'; +} +.event-list .event .event-details.event-description::before { + content: '\f024'; +} +.event-list .event-past { + --event-background: #f5f5f5; + --event-foreground: #999; + --event-title: #222; +} +@-moz-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@-webkit-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@-o-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +ul.breadcrumb { + font-size: 0.75em; + list-style: none; + margin: 1em 0; + padding: 0 2em; + text-align: center; +} +ul.breadcrumb li { + display: inline; +} +ul.breadcrumb li:not(:first-child)::before { + content: '/\00a0'; + font-weight: normal; + padding: 0.5em; +} +ul.breadcrumb li:last-child { + font-weight: bold; +} +.tag-cloud { + text-align: center; +} +.tag-cloud a { + display: inline-block; + margin: 10px; +} +.tag-cloud-0 { + border-bottom-color: #aaa; + color: #aaa; +} +.tag-cloud-1 { + border-bottom-color: #9a9a9a; + color: #9a9a9a; +} +.tag-cloud-2 { + border-bottom-color: #8b8b8b; + color: #8b8b8b; +} +.tag-cloud-3 { + border-bottom-color: #7c7c7c; + color: #7c7c7c; +} +.tag-cloud-4 { + border-bottom-color: #6c6c6c; + color: #6c6c6c; +} +.tag-cloud-5 { + border-bottom-color: #5d5d5d; + color: #5d5d5d; +} +.tag-cloud-6 { + border-bottom-color: #4e4e4e; + color: #4e4e4e; +} +.tag-cloud-7 { + border-bottom-color: #3e3e3e; + color: #3e3e3e; +} +.tag-cloud-8 { + border-bottom-color: #2f2f2f; + color: #2f2f2f; +} +.tag-cloud-9 { + border-bottom-color: #202020; + color: #202020; +} +.tag-cloud-10 { + border-bottom-color: #111; + color: #111; +} +.search-active { + overflow: hidden; +} +.search-pop-overlay { + background: rgba(0,0,0,0); + display: flex; + height: 100%; + left: 0; + position: fixed; + top: 0; + transition: visibility 0.4s, background 0.4s; + visibility: hidden; + width: 100%; + z-index: 40; +} +.search-active .search-pop-overlay { + background: rgba(0,0,0,0.3); + visibility: visible; +} +.search-popup { + background: var(--card-bg-color); + border-radius: 5px; + height: 80%; + margin: auto; + transform: scale(0); + transition: transform 0.4s; + width: 700px; +} +.search-active .search-popup { + transform: scale(1); +} +@media (max-width: 767px) { + .search-popup { + border-radius: 0; + height: 100%; + width: 100%; + } +} +.search-popup .search-icon, +.search-popup .popup-btn-close { + color: #999; + font-size: 18px; + padding: 0 10px; +} +.search-popup .popup-btn-close { + cursor: pointer; +} +.search-popup .popup-btn-close:hover .fa { + color: #222; +} +.search-popup .search-header { + background: #eee; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + display: flex; + padding: 5px; +} +.search-popup input.search-input { + background: transparent; + border: 0; + outline: 0; + width: 100%; +} +.search-popup input.search-input::-webkit-search-cancel-button { + display: none; +} +.search-popup .search-result-container { + height: calc(100% - 55px); + overflow: auto; + padding: 5px 25px; +} +.search-popup .search-result-container hr { + margin: 5px 0 10px; +} +.search-popup .search-result-container hr:first-child { + display: none; +} +.search-popup .search-result-list { + margin: 0 5px; + padding: 0; +} +.search-popup a.search-result-title { + font-weight: bold; +} +.search-popup p.search-result { + border-bottom: 1px dashed #ccc; + padding: 5px 0; +} +.search-input-container { + flex-grow: 1; +} +.search-input-container form { + padding: 2px; +} +.search-stats { + align-items: center; + display: flex; + justify-content: space-between; +} +.search-stats img { + height: 1em; + margin: 0; +} +.algolia-pagination { + margin: 40px 0; + opacity: 1; + padding: 0; +} +.algolia-pagination .pagination-item { + display: inline-block; +} +.algolia-pagination .current .page-number { + cursor: default; +} +.algolia-pagination .disabled-item { + visibility: hidden; +} +mjx-container[jax='CHTML'][display='true'], +.has-jax { + overflow: auto hidden; +} +mjx-container[display='true'] + br { + display: none; +} +.use-motion .animated { + animation-fill-mode: none; + visibility: inherit; +} +.use-motion .sidebar .animated { + animation-fill-mode: both; +} +.header-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + width: 240px; +} +@media (max-width: 991px) { + .header-inner { + border-radius: initial; + width: auto; + } +} +.main { + align-items: stretch; + display: flex; + justify-content: space-between; + margin: 0 auto; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .main { + width: 1160px; + } +} +@media (min-width: 1600px) { + .main { + width: 73%; + } +} +@media (max-width: 991px) { + .main { + display: block; + width: auto; + } +} +.main-inner { + border-radius: initial; + box-sizing: border-box; + width: calc(100% - 252px); +} +@media (max-width: 991px) { + .main-inner { + border-radius: initial; + width: 100%; + } +} +.footer-inner { + padding-left: 252px; +} +@media (max-width: 991px) { + .footer-inner { + padding-left: 0; + padding-right: 0; + width: auto; + } +} +.site-brand-container { + background: var(--theme-color); +} +@media (max-width: 991px) { + .site-nav-on .site-brand-container { + box-shadow: 0 0 16px rgba(0,0,0,0.5); + } +} +.site-meta { + padding: 20px 0; +} +.brand { + padding: 0; +} +.site-subtitle { + margin: 10px 10px 0; +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: #fff; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: #fff; +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav { + --scroll-height: 0; + height: 0; + overflow: hidden; + transition: height 0.2s ease-in-out; + } + body:not(.site-nav-on) .site-nav .animated { + animation: none; + } + body.site-nav-on .site-nav { + height: var(--scroll-height); + } +} +.menu .menu-item { + display: block; + margin: 0; +} +.menu .menu-item a { + padding: 5px 20px; + position: relative; + text-align: left; + transition-property: background-color; +} +@media (max-width: 991px) { + .menu .menu-item.menu-item-search { + display: none; + } +} +.menu .menu-item .badge { + background: #ccc; + border-radius: 10px; + color: var(--content-bg-color); + float: right; + padding: 2px 5px; + text-shadow: 1px 1px 0 rgba(0,0,0,0.1); +} +.main-menu .menu-item-active::after { + background: #bbb; + border-radius: 50%; + content: ' '; + height: 6px; + margin-top: -3px; + position: absolute; + right: 15px; + top: 50%; + width: 6px; +} +.sub-menu { + margin: 0; + padding: 6px 0; +} +.sub-menu .menu-item { + display: inline-block; +} +.sub-menu .menu-item a { + background: transparent; + margin: 5px 10px; + padding: initial; +} +.sub-menu .menu-item a:hover { + background: transparent; + color: #fc6423; +} +.sub-menu .menu-item-active { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sub-menu .menu-item-active:hover { + border-bottom-color: #fc6423; +} +.sidebar { + margin-top: 12px; + position: -webkit-sticky; + position: sticky; + top: 12px; + width: 240px; +} +@media (max-width: 991px) { + .sidebar { + display: none; + } +} +.sidebar-toggle { + display: none; +} +.sidebar-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + box-sizing: border-box; + color: var(--text-color); +} +.site-state-item { + padding: 0 10px; +} +.sidebar .sidebar-button { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; +} +.sidebar .sidebar-button button { + border: 0; + color: #fc6423; + display: block; + width: 100%; +} +.sidebar .sidebar-button button:hover { + background: none; + border: 0; + color: #e34603; +} +.links-of-author { + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.links-of-author-item { + margin: 5px 0 0; + width: 50%; +} +.links-of-author-item a { + box-sizing: border-box; + display: inline-block; + max-width: 100%; + overflow: hidden; + padding: 0 5px; + text-overflow: ellipsis; + white-space: nowrap; +} +.links-of-author-item a { + border-bottom: 0; + border-radius: 4px; + display: block; +} +.links-of-author-item a:hover { + background: var(--body-bg-color); +} +.main-inner { + background: var(--content-bg-color); + box-shadow: initial; + padding: 40px; +} +@media (max-width: 991px) { + .main-inner { + padding: 20px; + } +} +.sub-menu { + border-bottom: 1px solid #ddd; +} +.post-block:first-of-type { + padding-top: 40px; +} +@media (max-width: 767px) { + .pagination { + margin-bottom: 10px; + } +} diff --git a/css/noscript.css b/css/noscript.css new file mode 100644 index 0000000..c98ca77 --- /dev/null +++ b/css/noscript.css @@ -0,0 +1,34 @@ +body { + margin-top: 2rem; +} +.use-motion .menu-item, +.use-motion .sidebar, +.use-motion .post-block, +.use-motion .pagination, +.use-motion .comments, +.use-motion .post-header, +.use-motion .post-body, +.use-motion .collection-header { + visibility: visible; +} +.use-motion header.header, +.use-motion .site-brand-container .toggle, +.use-motion .footer { + opacity: initial; +} +.use-motion .site-title, +.use-motion .site-subtitle, +.use-motion .custom-logo-image { + opacity: initial; + top: initial; +} +.use-motion .logo-line { + transform: scaleX(1); +} +.search-pop-overlay, +.sidebar-nav { + display: none; +} +.sidebar-panel { + display: block; +} diff --git a/images/apple-touch-icon-next.png b/images/apple-touch-icon-next.png new file mode 100644 index 0000000..86a0d1d Binary files /dev/null and b/images/apple-touch-icon-next.png differ diff --git a/images/avatar.gif b/images/avatar.gif new file mode 100644 index 0000000..3b5d744 Binary files /dev/null and b/images/avatar.gif differ diff --git a/images/favicon-16x16-next.png b/images/favicon-16x16-next.png new file mode 100644 index 0000000..de8c5d3 Binary files /dev/null and b/images/favicon-16x16-next.png differ diff --git a/images/favicon-32x32-next.png b/images/favicon-32x32-next.png new file mode 100644 index 0000000..e02f5f4 Binary files /dev/null and b/images/favicon-32x32-next.png differ diff --git a/images/logo-algolia-nebula-blue-full.svg b/images/logo-algolia-nebula-blue-full.svg new file mode 100644 index 0000000..886c422 --- /dev/null +++ b/images/logo-algolia-nebula-blue-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 0000000..0f2b8d8 --- /dev/null +++ b/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..9eb663b --- /dev/null +++ b/index.html @@ -0,0 +1,1279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

During my internship at HPCC Systems in 2023, I had the incredible +opportunity to work on a dynamic project that pushed the boundaries of +my technical skills and knowledge. In this post, I will summarize the +key aspects and achievements of my internship project, highlighting the +challenges I faced, the solutions I implemented, and the valuable +lessons I learned along the way.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

In HPCC Systems, pictures need to be uploaded to HPCC Systems before +they can be accessed. This blog post documents how to upload an image to +HPCC Systems

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

ECL is not a procedural language, that is, the order in which +operations are performed is not necessarily the order given by the code, +which can cause trouble in calculating the execution time of operations. +This blog describes how to calculate the execution time of certain +operations for users to evaluate performance.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + + +
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

According to the configuration instructions provided by TensorFlow +and CUDA, I installed and configured the environment, but HPCC Systems +couldn't detect the GPU. This blog post documents the process of how I +resolved this issue.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

When upgrading the HPCC GNN bundle from TensorFlow1.x to +TensorFlow2.x, I encountered an error of +numpy() is only available when eager execution is enabled. +This blog records the cause and solution of the problem.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

In the ECL language, operations such as OUTPUT cannot be performed in +the definition, complicating our view of intermediate variables. This +blog introduces debugging tips for several ECL programming language.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

Learn the basics of ECL, the powerful programming language built for +big data analytics.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

Install directly in the system without using Anaconda.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

HPCC Systems is a platform +purpose-built for high-speed data engineering. This article shows how to +build HPCC Platform and set up ECL IDE.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/bookmark.js b/js/bookmark.js new file mode 100644 index 0000000..8e3ae6a --- /dev/null +++ b/js/bookmark.js @@ -0,0 +1,56 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + 'use strict'; + + const doSaveScroll = () => { + localStorage.setItem('bookmark' + location.pathname, window.scrollY); + }; + + const scrollToMark = () => { + let top = localStorage.getItem('bookmark' + location.pathname); + top = parseInt(top, 10); + // If the page opens with a specific hash, just jump out + if (!isNaN(top) && location.hash === '') { + // Auto scroll to the position + window.anime({ + targets : document.scrollingElement, + duration : 200, + easing : 'linear', + scrollTop: top + }); + } + }; + // Register everything + const init = function(trigger) { + // Create a link element + const link = document.querySelector('.book-mark-link'); + // Scroll event + window.addEventListener('scroll', () => link.classList.toggle('book-mark-link-fixed', window.scrollY === 0), { passive: true }); + // Register beforeunload event when the trigger is auto + if (trigger === 'auto') { + // Register beforeunload event + window.addEventListener('beforeunload', doSaveScroll); + document.addEventListener('pjax:send', doSaveScroll); + } + // Save the position by clicking the icon + link.addEventListener('click', () => { + doSaveScroll(); + window.anime({ + targets : link, + duration: 200, + easing : 'linear', + top : -30, + complete: () => { + setTimeout(() => { + link.style.top = ''; + }, 400); + } + }); + }); + scrollToMark(); + document.addEventListener('pjax:success', scrollToMark); + }; + + init(CONFIG.bookmark.save); +}); diff --git a/js/comments-buttons.js b/js/comments-buttons.js new file mode 100644 index 0000000..505c21b --- /dev/null +++ b/js/comments-buttons.js @@ -0,0 +1,25 @@ +/* global CONFIG */ + +(function() { + const commentButton = document.querySelectorAll('.comment-button'); + commentButton.forEach(element => { + const commentClass = element.classList[2]; + element.addEventListener('click', () => { + commentButton.forEach(active => active.classList.toggle('active', active === element)); + document.querySelectorAll('.comment-position').forEach(active => active.classList.toggle('active', active.classList.contains(commentClass))); + if (CONFIG.comments.storage) { + localStorage.setItem('comments_active', commentClass); + } + }); + }); + let { activeClass } = CONFIG.comments; + if (CONFIG.comments.storage) { + activeClass = localStorage.getItem('comments_active') || activeClass; + } + if (activeClass) { + const activeButton = document.querySelector(`.comment-button.${activeClass}`); + if (activeButton) { + activeButton.click(); + } + } +})(); diff --git a/js/comments.js b/js/comments.js new file mode 100644 index 0000000..4045e8c --- /dev/null +++ b/js/comments.js @@ -0,0 +1,21 @@ +/* global CONFIG */ + +window.addEventListener('tabs:register', () => { + let { activeClass } = CONFIG.comments; + if (CONFIG.comments.storage) { + activeClass = localStorage.getItem('comments_active') || activeClass; + } + if (activeClass) { + const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`); + if (activeTab) { + activeTab.click(); + } + } +}); +if (CONFIG.comments.storage) { + window.addEventListener('tabs:click', event => { + if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; + const commentClass = event.target.classList[1]; + localStorage.setItem('comments_active', commentClass); + }); +} diff --git a/js/config.js b/js/config.js new file mode 100644 index 0000000..caa0075 --- /dev/null +++ b/js/config.js @@ -0,0 +1,66 @@ +if (!window.NexT) window.NexT = {}; + +(function() { + const className = 'next-config'; + + const staticConfig = {}; + let variableConfig = {}; + + const parse = text => JSON.parse(text || '{}'); + + const update = name => { + const targetEle = document.querySelector(`.${className}[data-name="${name}"]`); + if (!targetEle) return; + const parsedConfig = parse(targetEle.text); + if (name === 'main') { + Object.assign(staticConfig, parsedConfig); + } else { + variableConfig[name] = parsedConfig; + } + }; + + update('main'); + + window.CONFIG = new Proxy({}, { + get(overrideConfig, name) { + let existing; + if (name in staticConfig) { + existing = staticConfig[name]; + } else { + if (!(name in variableConfig)) update(name); + existing = variableConfig[name]; + } + + // For unset override and mixable existing + if (!(name in overrideConfig) && typeof existing === 'object') { + // Get ready to mix. + overrideConfig[name] = {}; + } + + if (name in overrideConfig) { + const override = overrideConfig[name]; + + // When mixable + if (typeof override === 'object' && typeof existing === 'object') { + // Mix, proxy changes to the override. + return new Proxy({ ...existing, ...override }, { + set(target, prop, value) { + target[prop] = value; + override[prop] = value; + return true; + } + }); + } + + return override; + } + + // Only when not mixable and override hasn't been set. + return existing; + } + }); + + document.addEventListener('pjax:success', () => { + variableConfig = {}; + }); +})(); diff --git a/js/motion.js b/js/motion.js new file mode 100644 index 0000000..2f38249 --- /dev/null +++ b/js/motion.js @@ -0,0 +1,125 @@ +/* global NexT, CONFIG */ + +NexT.motion = {}; + +NexT.motion.integrator = { + queue: [], + init : function() { + this.queue = []; + return this; + }, + add: function(fn) { + const sequence = fn(); + if (CONFIG.motion.async) this.queue.push(sequence); + else this.queue = this.queue.concat(sequence); + return this; + }, + bootstrap: function() { + if (!CONFIG.motion.async) this.queue = [this.queue]; + this.queue.forEach(sequence => { + const timeline = window.anime.timeline({ + duration: 200, + easing : 'linear' + }); + sequence.forEach(item => { + if (item.deltaT) timeline.add(item, item.deltaT); + else timeline.add(item); + }); + }); + } +}; + +NexT.motion.middleWares = { + header: function() { + const sequence = []; + + function getMistLineSettings(targets) { + sequence.push({ + targets, + scaleX : [0, 1], + duration: 500, + deltaT : '-=200' + }); + } + + function pushToSequence(targets, sequenceQueue = false) { + sequence.push({ + targets, + opacity: 1, + top : 0, + deltaT : sequenceQueue ? '-=200' : '-=0' + }); + } + + pushToSequence('header.header'); + CONFIG.scheme === 'Mist' && getMistLineSettings('.logo-line'); + CONFIG.scheme === 'Muse' && pushToSequence('.custom-logo-image'); + pushToSequence('.site-title'); + pushToSequence('.site-brand-container .toggle', true); + pushToSequence('.site-subtitle'); + (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && pushToSequence('.custom-logo-image'); + + document.querySelectorAll('.menu-item').forEach(targets => { + sequence.push({ + targets, + complete: () => targets.classList.add('animated', 'fadeInDown'), + deltaT : '-=200' + }); + }); + + return sequence; + }, + + subMenu: function() { + const subMenuItem = document.querySelectorAll('.sub-menu .menu-item'); + if (subMenuItem.length > 0) { + subMenuItem.forEach(element => { + element.classList.add('animated'); + }); + } + return []; + }, + + postList: function() { + const sequence = []; + const { post_block, post_header, post_body, coll_header } = CONFIG.motion.transition; + + function animate(animation, selector) { + if (!animation) return; + document.querySelectorAll(selector).forEach(targets => { + sequence.push({ + targets, + complete: () => targets.classList.add('animated', animation), + deltaT : '-=100' + }); + }); + } + + animate(post_block, '.post-block, .pagination, .comments'); + animate(coll_header, '.collection-header'); + animate(post_header, '.post-header'); + animate(post_body, '.post-body'); + + return sequence; + }, + + sidebar: function() { + const sidebar = document.querySelector('.sidebar'); + const sidebarTransition = CONFIG.motion.transition.sidebar; + // Only for Pisces | Gemini. + if (sidebarTransition && (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini')) { + return [{ + targets : sidebar, + complete: () => sidebar.classList.add('animated', sidebarTransition) + }]; + } + return []; + }, + + footer: function() { + return [{ + targets: document.querySelector('.footer'), + opacity: 1 + }]; + } +}; diff --git a/js/next-boot.js b/js/next-boot.js new file mode 100644 index 0000000..1225fd2 --- /dev/null +++ b/js/next-boot.js @@ -0,0 +1,75 @@ +/* global NexT, CONFIG */ + +NexT.boot = {}; + +NexT.boot.registerEvents = function() { + + NexT.utils.registerScrollPercent(); + NexT.utils.registerCanIUseTag(); + + // Mobile top menu bar. + document.querySelector('.site-nav-toggle .toggle').addEventListener('click', event => { + event.currentTarget.classList.toggle('toggle-close'); + const siteNav = document.querySelector('.site-nav'); + if (!siteNav) return; + siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px'); + document.body.classList.toggle('site-nav-on'); + }); + + document.querySelectorAll('.sidebar-nav li').forEach((element, index) => { + element.addEventListener('click', () => { + NexT.utils.activateSidebarPanel(index); + }); + }); + + window.addEventListener('hashchange', () => { + const tHash = location.hash; + if (tHash !== '' && !tHash.match(/%\S{2}/)) { + const target = document.querySelector(`.tabs ul.nav-tabs li a[href="${tHash}"]`); + target && target.click(); + } + }); +}; + +NexT.boot.refresh = function() { + + /** + * Register JS handlers by condition option. + * Need to add config option in Front-End at 'scripts/helpers/next-config.js' file. + */ + CONFIG.prism && window.Prism.highlightAll(); + CONFIG.mediumzoom && window.mediumZoom('.post-body :not(a) > img, .post-body > img', { + background: 'var(--content-bg-color)' + }); + CONFIG.lazyload && window.lozad('.post-body img').observe(); + CONFIG.pangu && window.pangu.spacingPage(); + + CONFIG.exturl && NexT.utils.registerExtURL(); + NexT.utils.wrapTableWithBox(); + NexT.utils.registerCopyCode(); + NexT.utils.registerTabsTag(); + NexT.utils.registerActiveMenuItem(); + NexT.utils.registerLangSelect(); + NexT.utils.registerSidebarTOC(); + NexT.utils.registerPostReward(); + NexT.utils.registerVideoIframe(); +}; + +NexT.boot.motion = function() { + // Define Motion Sequence & Bootstrap Motion. + if (CONFIG.motion.enable) { + NexT.motion.integrator + .add(NexT.motion.middleWares.header) + .add(NexT.motion.middleWares.postList) + .add(NexT.motion.middleWares.sidebar) + .add(NexT.motion.middleWares.footer) + .bootstrap(); + } + NexT.utils.updateSidebarPosition(); +}; + +document.addEventListener('DOMContentLoaded', () => { + NexT.boot.registerEvents(); + NexT.boot.refresh(); + NexT.boot.motion(); +}); diff --git a/js/pjax.js b/js/pjax.js new file mode 100644 index 0000000..da61136 --- /dev/null +++ b/js/pjax.js @@ -0,0 +1,34 @@ +/* global NexT, CONFIG, Pjax */ + +const pjax = new Pjax({ + selectors: [ + 'head title', + 'script[type="application/json"]', + '.main-inner', + '.post-toc-wrap', + '.languages', + '.pjax' + ], + analytics: false, + cacheBust: false, + scrollTo : !CONFIG.bookmark.enable +}); + +document.addEventListener('pjax:success', () => { + pjax.executeScripts(document.querySelectorAll('script[data-pjax]')); + NexT.boot.refresh(); + // Define Motion Sequence & Bootstrap Motion. + if (CONFIG.motion.enable) { + NexT.motion.integrator + .init() + .add(NexT.motion.middleWares.subMenu) + .add(NexT.motion.middleWares.postList) + .bootstrap(); + } + if (CONFIG.sidebar.display !== 'remove') { + const hasTOC = document.querySelector('.post-toc'); + document.querySelector('.sidebar-inner').classList.toggle('sidebar-nav-active', hasTOC); + NexT.utils.activateSidebarPanel(hasTOC ? 0 : 1); + NexT.utils.updateSidebarPosition(); + } +}); diff --git a/js/schedule.js b/js/schedule.js new file mode 100644 index 0000000..8f0c26c --- /dev/null +++ b/js/schedule.js @@ -0,0 +1,138 @@ +/* global CONFIG */ + +// https://developers.google.com/calendar/api/v3/reference/events/list +(function() { + // Initialization + const calendar = { + orderBy : 'startTime', + showLocation: false, + offsetMax : 72, + offsetMin : 4, + showDeleted : false, + singleEvents: true, + maxResults : 250 + }; + + // Read config form theme config file + Object.assign(calendar, CONFIG.calendar); + + const now = new Date(); + const timeMax = new Date(); + const timeMin = new Date(); + + timeMax.setHours(now.getHours() + calendar.offsetMax); + timeMin.setHours(now.getHours() - calendar.offsetMin); + + // Build URL + const params = { + key : calendar.api_key, + orderBy : calendar.orderBy, + timeMax : timeMax.toISOString(), + timeMin : timeMin.toISOString(), + showDeleted : calendar.showDeleted, + singleEvents: calendar.singleEvents, + maxResults : calendar.maxResults + }; + + const request_url = new URL(`https://www.googleapis.com/calendar/v3/calendars/${calendar.calendar_id}/events`); + Object.entries(params).forEach(param => request_url.searchParams.append(...param)); + + function getRelativeTime(current, previous) { + const msPerMinute = 60 * 1000; + const msPerHour = msPerMinute * 60; + const msPerDay = msPerHour * 24; + const msPerMonth = msPerDay * 30; + const msPerYear = msPerDay * 365; + + let elapsed = current - previous; + const tense = elapsed > 0 ? ' ago' : ' later'; + + elapsed = Math.abs(elapsed); + + if (elapsed < msPerHour) { + return Math.round(elapsed / msPerMinute) + ' minutes' + tense; + } else if (elapsed < msPerDay) { + return Math.round(elapsed / msPerHour) + ' hours' + tense; + } else if (elapsed < msPerMonth) { + return 'about ' + Math.round(elapsed / msPerDay) + ' days' + tense; + } else if (elapsed < msPerYear) { + return 'about ' + Math.round(elapsed / msPerMonth) + ' months' + tense; + } + + return 'about ' + Math.round(elapsed / msPerYear) + ' years' + tense; + } + + function buildEventDOM(tense, event, start, end) { + const durationFormat = { + weekday: 'short', + hour : '2-digit', + minute : '2-digit' + }; + const relativeTime = tense === 'now' ? 'NOW' : getRelativeTime(now, start); + const duration = start.toLocaleTimeString([], durationFormat) + ' - ' + end.toLocaleTimeString([], durationFormat); + + let location = ''; + if (calendar.showLocation && event.location) { + location = `${event.location}`; + } + let description = ''; + if (event.description) { + description = `${event.description}`; + } + + const eventContent = `
+

+ ${event.summary} + ${relativeTime} +

+ ${location} + ${duration} + ${description} +
`; + return eventContent; + } + + function fetchData() { + const eventList = document.querySelector('.event-list'); + if (!eventList) return; + + fetch(request_url.href).then(response => { + return response.json(); + }).then(data => { + if (data.items.length === 0) { + eventList.innerHTML = '
'; + return; + } + // Clean the event list + eventList.innerHTML = ''; + let prevEnd = 0; // used to decide where to insert an
+ const utc = new Date().getTimezoneOffset() * 60000; + + data.items.forEach(event => { + // Parse data + const start = new Date(event.start.dateTime || (new Date(event.start.date).getTime() + utc)); + const end = new Date(event.end.dateTime || (new Date(event.end.date).getTime() + utc)); + + let tense = 'now'; + if (end < now) { + tense = 'past'; + } else if (start > now) { + tense = 'future'; + } + + if (tense === 'future' && prevEnd < now) { + eventList.insertAdjacentHTML('beforeend', '
'); + } + + eventList.insertAdjacentHTML('beforeend', buildEventDOM(tense, event, start, end)); + prevEnd = end; + }); + }); + } + + fetchData(); + const fetchDataTimer = setInterval(fetchData, 60000); + document.addEventListener('pjax:send', () => { + clearInterval(fetchDataTimer); + }); +})(); diff --git a/js/schemes/muse.js b/js/schemes/muse.js new file mode 100644 index 0000000..ae5addb --- /dev/null +++ b/js/schemes/muse.js @@ -0,0 +1,60 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + + const isRight = CONFIG.sidebar.position === 'right'; + + const sidebarToggleMotion = { + mouse: {}, + init : function() { + window.addEventListener('mousedown', this.mousedownHandler.bind(this)); + window.addEventListener('mouseup', this.mouseupHandler.bind(this)); + document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); + window.addEventListener('sidebar:show', this.showSidebar); + window.addEventListener('sidebar:hide', this.hideSidebar); + }, + mousedownHandler: function(event) { + this.mouse.X = event.pageX; + this.mouse.Y = event.pageY; + }, + mouseupHandler: function(event) { + const deltaX = event.pageX - this.mouse.X; + const deltaY = event.pageY - this.mouse.Y; + const clickingBlankPart = Math.hypot(deltaX, deltaY) < 20 && event.target.matches('.main'); + // Fancybox has z-index property, but medium-zoom does not, so the sidebar will overlay the zoomed image. + if (clickingBlankPart || event.target.matches('img.medium-zoom-image')) { + this.hideSidebar(); + } + }, + clickHandler: function() { + document.body.classList.contains('sidebar-active') ? this.hideSidebar() : this.showSidebar(); + }, + showSidebar: function() { + document.body.classList.add('sidebar-active'); + const animateAction = isRight ? 'fadeInRight' : 'fadeInLeft'; + document.querySelectorAll('.sidebar .animated').forEach((element, index) => { + element.style.animationDelay = (100 * index) + 'ms'; + element.classList.remove(animateAction); + setTimeout(() => { + // Trigger a DOM reflow + element.classList.add(animateAction); + }); + }); + }, + hideSidebar: function() { + document.body.classList.remove('sidebar-active'); + } + }; + if (CONFIG.sidebar.display !== 'remove') sidebarToggleMotion.init(); + + function updateFooterPosition() { + const footer = document.querySelector('.footer'); + const containerHeight = document.querySelector('header.header').offsetHeight + document.querySelector('.main').offsetHeight + footer.offsetHeight; + footer.classList.toggle('footer-fixed', containerHeight <= window.innerHeight); + } + + updateFooterPosition(); + window.addEventListener('resize', updateFooterPosition); + window.addEventListener('scroll', updateFooterPosition, { passive: true }); +}); diff --git a/js/third-party/analytics/baidu-analytics.js b/js/third-party/analytics/baidu-analytics.js new file mode 100644 index 0000000..c10e7d0 --- /dev/null +++ b/js/third-party/analytics/baidu-analytics.js @@ -0,0 +1,7 @@ +/* global _hmt */ + +if (!window._hmt) window._hmt = []; + +document.addEventListener('pjax:success', () => { + _hmt.push(['_trackPageview', location.pathname]); +}); diff --git a/js/third-party/analytics/google-analytics.js b/js/third-party/analytics/google-analytics.js new file mode 100644 index 0000000..2cd128f --- /dev/null +++ b/js/third-party/analytics/google-analytics.js @@ -0,0 +1,35 @@ +/* global CONFIG, dataLayer, gtag */ + +if (!CONFIG.google_analytics.only_pageview) { + if (CONFIG.hostname === location.hostname) { + window.dataLayer = window.dataLayer || []; + window.gtag = function() { + dataLayer.push(arguments); + }; + gtag('js', new Date()); + gtag('config', CONFIG.google_analytics.tracking_id); + + document.addEventListener('pjax:success', () => { + gtag('event', 'page_view', { + page_location: location.href, + page_path : location.pathname, + page_title : document.title + }); + }); + } +} else { + const sendPageView = () => { + if (CONFIG.hostname !== location.hostname) return; + const uid = localStorage.getItem('uid') || (Math.random() + '.' + Math.random()); + localStorage.setItem('uid', uid); + navigator.sendBeacon('https://www.google-analytics.com/collect', new URLSearchParams({ + v : 1, + tid: CONFIG.google_analytics.tracking_id, + cid: uid, + t : 'pageview', + dp : encodeURIComponent(location.pathname) + })); + }; + document.addEventListener('pjax:complete', sendPageView); + sendPageView(); +} diff --git a/js/third-party/analytics/growingio.js b/js/third-party/analytics/growingio.js new file mode 100644 index 0000000..0460833 --- /dev/null +++ b/js/third-party/analytics/growingio.js @@ -0,0 +1,10 @@ +/* global CONFIG, gio */ + +if (!window.gio) { + window.gio = function() { + (window.gio.q = window.gio.q || []).push(arguments); + }; +} + +gio('init', `${CONFIG.growingio_analytics}`, {}); +gio('send'); diff --git a/js/third-party/analytics/matomo.js b/js/third-party/analytics/matomo.js new file mode 100644 index 0000000..290a3e0 --- /dev/null +++ b/js/third-party/analytics/matomo.js @@ -0,0 +1,19 @@ +/* global CONFIG */ + +if (CONFIG.matomo.enable) { + window._paq = window._paq || []; + const _paq = window._paq; + + /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); + const u = CONFIG.matomo.server_url; + _paq.push(['setTrackerUrl', u + 'matomo.php']); + _paq.push(['setSiteId', CONFIG.matomo.site_id]); + const d = document; + const g = d.createElement('script'); + const s = d.getElementsByTagName('script')[0]; + g.async = true; + g.src = u + 'matomo.js'; + s.parentNode.insertBefore(g, s); +} diff --git a/js/third-party/chat/chatra.js b/js/third-party/chat/chatra.js new file mode 100644 index 0000000..e495b8e --- /dev/null +++ b/js/third-party/chat/chatra.js @@ -0,0 +1,19 @@ +/* global CONFIG, Chatra */ + +(function() { + if (CONFIG.chatra.embed) { + window.ChatraSetup = { + mode : 'frame', + injectTo: CONFIG.chatra.embed + }; + } + + window.ChatraID = CONFIG.chatra.id; + + const chatButton = document.querySelector('.sidebar-button button'); + if (chatButton) { + chatButton.addEventListener('click', () => { + Chatra('openChat', true); + }); + } +})(); diff --git a/js/third-party/chat/gitter.js b/js/third-party/chat/gitter.js new file mode 100644 index 0000000..2b26d05 --- /dev/null +++ b/js/third-party/chat/gitter.js @@ -0,0 +1,5 @@ +/* global CONFIG */ + +((window.gitter = {}).chat = {}).options = { + room: CONFIG.gitter.room +}; diff --git a/js/third-party/chat/tidio.js b/js/third-party/chat/tidio.js new file mode 100644 index 0000000..bffb918 --- /dev/null +++ b/js/third-party/chat/tidio.js @@ -0,0 +1,10 @@ +/* global tidioChatApi */ + +(function() { + const chatButton = document.querySelector('.sidebar-button button'); + if (chatButton) { + chatButton.addEventListener('click', () => { + tidioChatApi.open(); + }); + } +})(); diff --git a/js/third-party/comments/changyan.js b/js/third-party/comments/changyan.js new file mode 100644 index 0000000..18a1be4 --- /dev/null +++ b/js/third-party/comments/changyan.js @@ -0,0 +1,39 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + const { appid, appkey } = CONFIG.changyan; + const mainJs = 'https://cy-cdn.kuaizhan.com/upload/changyan.js'; + const countJs = `https://cy-cdn.kuaizhan.com/upload/plugins/plugins.list.count.js?clientId=${appid}`; + + // Get the number of comments + setTimeout(() => { + return NexT.utils.getScript(countJs, { + attributes: { + async: true, + id : 'cy_cmt_num' + } + }); + }, 0); + + // When scroll to comment section + if (CONFIG.page.comments && !CONFIG.page.isHome) { + NexT.utils.loadComments('#SOHUCS') + .then(() => { + return NexT.utils.getScript(mainJs, { + attributes: { + async: true + } + }); + }) + .then(() => { + window.changyan.api.config({ + appid, + conf: appkey + }); + }) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Failed to load Changyan', error); + }); + } +}); diff --git a/js/third-party/comments/disqus.js b/js/third-party/comments/disqus.js new file mode 100644 index 0000000..381c26f --- /dev/null +++ b/js/third-party/comments/disqus.js @@ -0,0 +1,41 @@ +/* global NexT, CONFIG, DISQUS */ + +document.addEventListener('page:loaded', () => { + + if (CONFIG.disqus.count) { + const loadCount = () => { + NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/count.js`, { + attributes: { id: 'dsq-count-scr' } + }); + }; + + // defer loading until the whole page loading is completed + window.addEventListener('load', loadCount, false); + } + + if (CONFIG.page.comments) { + // `disqus_config` should be a global variable + // See https://help.disqus.com/en/articles/1717084-javascript-configuration-variables + window.disqus_config = function() { + this.page.url = CONFIG.page.permalink; + this.page.identifier = CONFIG.page.path; + this.page.title = CONFIG.page.title; + if (CONFIG.disqus.i18n.disqus !== 'disqus') { + this.language = CONFIG.disqus.i18n.disqus; + } + }; + NexT.utils.loadComments('#disqus_thread').then(() => { + if (window.DISQUS) { + DISQUS.reset({ + reload: true, + config: window.disqus_config + }); + } else { + NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/embed.js`, { + attributes: { dataset: { timestamp: '' + +new Date() } } + }); + } + }); + } + +}); diff --git a/js/third-party/comments/disqusjs.js b/js/third-party/comments/disqusjs.js new file mode 100644 index 0000000..f22579a --- /dev/null +++ b/js/third-party/comments/disqusjs.js @@ -0,0 +1,23 @@ +/* global NexT, CONFIG, DisqusJS */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('#disqus_thread') + .then(() => NexT.utils.getScript(CONFIG.disqusjs.js, { condition: window.DisqusJS })) + .then(() => { + window.dsqjs = new DisqusJS({ + api : CONFIG.disqusjs.api || 'https://disqus.com/api/', + apikey : CONFIG.disqusjs.apikey, + shortname : CONFIG.disqusjs.shortname, + url : CONFIG.page.permalink, + identifier: CONFIG.page.path, + title : CONFIG.page.title + }); + window.dsqjs.render(document.querySelector('.disqusjs')); + }); +}); + +document.addEventListener('pjax:send', () => { + if (window.dsqjs) window.dsqjs.destroy(); +}); diff --git a/js/third-party/comments/gitalk.js b/js/third-party/comments/gitalk.js new file mode 100644 index 0000000..08d07f4 --- /dev/null +++ b/js/third-party/comments/gitalk.js @@ -0,0 +1,24 @@ +/* global NexT, CONFIG, Gitalk */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('.gitalk-container') + .then(() => NexT.utils.getScript(CONFIG.gitalk.js, { + condition: window.Gitalk + })) + .then(() => { + const gitalk = new Gitalk({ + clientID : CONFIG.gitalk.client_id, + clientSecret : CONFIG.gitalk.client_secret, + repo : CONFIG.gitalk.repo, + owner : CONFIG.gitalk.github_id, + admin : [CONFIG.gitalk.admin_user], + id : CONFIG.gitalk.path_md5, + proxy : CONFIG.gitalk.proxy, + language : CONFIG.gitalk.language || window.navigator.language, + distractionFreeMode: CONFIG.gitalk.distraction_free_mode + }); + gitalk.render(document.querySelector('.gitalk-container')); + }); +}); diff --git a/js/third-party/comments/isso.js b/js/third-party/comments/isso.js new file mode 100644 index 0000000..2c70601 --- /dev/null +++ b/js/third-party/comments/isso.js @@ -0,0 +1,15 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('#isso-thread') + .then(() => NexT.utils.getScript(`${CONFIG.isso}js/embed.min.js`, { + attributes: { + dataset: { + isso: `${CONFIG.isso}` + } + }, + parentNode: document.querySelector('#isso-thread') + })); +}); diff --git a/js/third-party/comments/livere.js b/js/third-party/comments/livere.js new file mode 100644 index 0000000..c4bcd2e --- /dev/null +++ b/js/third-party/comments/livere.js @@ -0,0 +1,19 @@ +/* global NexT, CONFIG, LivereTower */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('#lv-container').then(() => { + window.livereOptions = { + refer: CONFIG.page.path.replace(/index\.html$/, '') + }; + + if (typeof LivereTower === 'function') return; + + NexT.utils.getScript('https://cdn-city.livere.com/js/embed.dist.js', { + attributes: { + async: true + } + }); + }); +}); diff --git a/js/third-party/comments/utterances.js b/js/third-party/comments/utterances.js new file mode 100644 index 0000000..332ee05 --- /dev/null +++ b/js/third-party/comments/utterances.js @@ -0,0 +1,17 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('.utterances-container') + .then(() => NexT.utils.getScript('https://utteranc.es/client.js', { + attributes: { + async : true, + crossOrigin : 'anonymous', + 'repo' : CONFIG.utterances.repo, + 'issue-term': CONFIG.utterances.issue_term, + 'theme' : CONFIG.utterances.theme + }, + parentNode: document.querySelector('.utterances-container') + })); +}); diff --git a/js/third-party/fancybox.js b/js/third-party/fancybox.js new file mode 100644 index 0000000..bb436ab --- /dev/null +++ b/js/third-party/fancybox.js @@ -0,0 +1,38 @@ +document.addEventListener('page:loaded', () => { + + /** + * Wrap images with fancybox. + */ + document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(element => { + const $image = $(element); + const imageLink = $image.attr('data-src') || $image.attr('src'); + const $imageWrapLink = $image.wrap(``).parent('a'); + if ($image.is('.post-gallery img')) { + $imageWrapLink.attr('data-fancybox', 'gallery').attr('rel', 'gallery'); + } else if ($image.is('.group-picture img')) { + $imageWrapLink.attr('data-fancybox', 'group').attr('rel', 'group'); + } else { + $imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default'); + } + + const imageTitle = $image.attr('title') || $image.attr('alt'); + if (imageTitle) { + // Do not append image-caption if pandoc has already created a figcaption + if (!$imageWrapLink.next('figcaption').length) { + $imageWrapLink.append(`

${imageTitle}

`); + } + // Make sure img title tag will show correctly in fancybox + $imageWrapLink.attr('title', imageTitle).attr('data-caption', imageTitle); + } + }); + + $.fancybox.defaults.hash = false; + $('.fancybox').fancybox({ + loop : true, + helpers: { + overlay: { + locked: false + } + } + }); +}); diff --git a/js/third-party/math/katex.js b/js/third-party/math/katex.js new file mode 100644 index 0000000..ad745b1 --- /dev/null +++ b/js/third-party/math/katex.js @@ -0,0 +1,7 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.enableMath) return; + + NexT.utils.getScript(CONFIG.katex.copy_tex_js).catch(() => {}); +}); diff --git a/js/third-party/math/mathjax.js b/js/third-party/math/mathjax.js new file mode 100644 index 0000000..fe4d448 --- /dev/null +++ b/js/third-party/math/mathjax.js @@ -0,0 +1,36 @@ +/* global NexT, CONFIG, MathJax */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.enableMath) return; + + if (typeof MathJax === 'undefined') { + window.MathJax = { + tex: { + inlineMath: { '[+]': [['$', '$']] }, + tags : CONFIG.mathjax.tags + }, + options: { + renderActions: { + insertedScript: [200, () => { + document.querySelectorAll('mjx-container').forEach(node => { + const target = node.parentNode; + if (target.nodeName.toLowerCase() === 'li') { + target.parentNode.classList.add('has-jax'); + } + }); + }, '', false] + } + } + }; + NexT.utils.getScript(CONFIG.mathjax.js, { + attributes: { + defer: true + } + }); + } else { + MathJax.startup.document.state(0); + MathJax.typesetClear(); + MathJax.texReset(); + MathJax.typesetPromise(); + } +}); diff --git a/js/third-party/pace.js b/js/third-party/pace.js new file mode 100644 index 0000000..c22d59f --- /dev/null +++ b/js/third-party/pace.js @@ -0,0 +1,7 @@ +/* global Pace */ + +Pace.options.restartOnPushState = false; + +document.addEventListener('pjax:send', () => { + Pace.restart(); +}); diff --git a/js/third-party/quicklink.js b/js/third-party/quicklink.js new file mode 100644 index 0000000..2543ad1 --- /dev/null +++ b/js/third-party/quicklink.js @@ -0,0 +1,37 @@ +/* global CONFIG, quicklink */ + +(function() { + if (typeof CONFIG.quicklink.ignores === 'string') { + const ignoresStr = `[${CONFIG.quicklink.ignores}]`; + CONFIG.quicklink.ignores = JSON.parse(ignoresStr); + } + + let resetFn = null; + + const onRefresh = () => { + if (resetFn) resetFn(); + if (!CONFIG.quicklink.enable) return; + + let ignoresArr = CONFIG.quicklink.ignores || []; + if (!Array.isArray(ignoresArr)) { + ignoresArr = [ignoresArr]; + } + + resetFn = quicklink.listen({ + timeout : CONFIG.quicklink.timeout, + priority: CONFIG.quicklink.priority, + ignores : [ + uri => uri.includes('#'), + uri => uri === CONFIG.quicklink.url, + ...ignoresArr + ] + }); + }; + + if (CONFIG.quicklink.delay) { + window.addEventListener('load', onRefresh); + document.addEventListener('pjax:success', onRefresh); + } else { + document.addEventListener('page:loaded', onRefresh); + } +})(); diff --git a/js/third-party/rating.js b/js/third-party/rating.js new file mode 100644 index 0000000..fc61a3a --- /dev/null +++ b/js/third-party/rating.js @@ -0,0 +1,22 @@ +/* global CONFIG, WPac */ + +(function() { + const widgets = [{ + widget: 'Rating', + id : CONFIG.rating.id, + el : 'wpac-rating', + color : CONFIG.rating.color + }]; + + document.addEventListener('page:loaded', () => { + if (!CONFIG.page.isPost) return; + + const newWidgets = widgets.map(widget => ({ ...widget })); + + if (window.WPac) { + WPac.init(newWidgets); + } else { + window.wpac_init = newWidgets; + } + }); +})(); diff --git a/js/third-party/search/algolia-search.js b/js/third-party/search/algolia-search.js new file mode 100644 index 0000000..f74bab5 --- /dev/null +++ b/js/third-party/search/algolia-search.js @@ -0,0 +1,115 @@ +/* global instantsearch, algoliasearch, CONFIG, pjax */ + +document.addEventListener('DOMContentLoaded', () => { + const { indexName, appID, apiKey, hits } = CONFIG.algolia; + + const search = instantsearch({ + indexName, + searchClient : algoliasearch(appID, apiKey), + searchFunction: helper => { + if (document.querySelector('.search-input').value) { + helper.search(); + } + } + }); + + if (typeof pjax === 'object') { + search.on('render', () => { + pjax.refresh(document.querySelector('.algolia-hits')); + }); + } + + // Registering Widgets + search.addWidgets([ + instantsearch.widgets.configure({ + hitsPerPage: hits.per_page || 10 + }), + + instantsearch.widgets.searchBox({ + container : '.search-input-container', + placeholder : CONFIG.i18n.placeholder, + // Hide default icons of algolia search + showReset : false, + showSubmit : false, + showLoadingIndicator: false, + cssClasses : { + input: 'search-input' + } + }), + + + instantsearch.widgets.hits({ + container : '.algolia-hits', + escapeHTML: false, + templates : { + item: data => { + const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult; + let result = `${title.value}`; + const content = excerpt || excerptStrip || contentStripTruncate; + if (content && content.value) { + const div = document.createElement('div'); + div.innerHTML = content.value; + result += `

${div.textContent.substring(0, 100)}...

`; + } + return result; + }, + empty: data => { + return `
+ ${CONFIG.i18n.empty.replace('${query}', data.query)} +
`; + } + }, + cssClasses: { + list: 'search-result-list' + } + }), + + instantsearch.widgets.pagination({ + container: '.algolia-pagination', + scrollTo : false, + showFirst: false, + showLast : false, + templates: { + first : '', + last : '', + previous: '', + next : '' + }, + cssClasses: { + list : ['pagination', 'algolia-pagination'], + item : 'pagination-item', + link : 'page-number', + selectedItem: 'current', + disabledItem: 'disabled-item' + } + }) + ]); + + search.start(); + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.classList.add('search-active'); + setTimeout(() => document.querySelector('.search-input').focus(), 500); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + document.addEventListener('pjax:success', onPopupClose); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); \ No newline at end of file diff --git a/js/third-party/search/local-search.js b/js/third-party/search/local-search.js new file mode 100644 index 0000000..92a264d --- /dev/null +++ b/js/third-party/search/local-search.js @@ -0,0 +1,99 @@ +/* global CONFIG, pjax, LocalSearch */ + +document.addEventListener('DOMContentLoaded', () => { + if (!CONFIG.path) { + // Search DB path + console.warn('`hexo-generator-searchdb` plugin is not installed!'); + return; + } + const localSearch = new LocalSearch({ + path : CONFIG.path, + top_n_per_article: CONFIG.localsearch.top_n_per_article, + unescape : CONFIG.localsearch.unescape + }); + + const input = document.querySelector('.search-input'); + + const inputEventFunction = () => { + if (!localSearch.isfetched) return; + const searchText = input.value.trim().toLowerCase(); + const keywords = searchText.split(/[-\s]+/); + const container = document.querySelector('.search-result-container'); + let resultItems = []; + if (searchText.length > 0) { + // Perform local searching + resultItems = localSearch.getResultItems(keywords); + } + if (keywords.length === 1 && keywords[0] === '') { + container.classList.add('no-result'); + container.innerHTML = '
'; + } else if (resultItems.length === 0) { + container.classList.add('no-result'); + container.innerHTML = '
'; + } else { + resultItems.sort((left, right) => { + if (left.includedCount !== right.includedCount) { + return right.includedCount - left.includedCount; + } else if (left.hitCount !== right.hitCount) { + return right.hitCount - left.hitCount; + } + return right.id - left.id; + }); + const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length); + + container.classList.remove('no-result'); + container.innerHTML = `
${stats}
+
+ `; + if (typeof pjax === 'object') pjax.refresh(container); + } + }; + + localSearch.highlightSearchWords(document.querySelector('.post-body')); + if (CONFIG.localsearch.preload) { + localSearch.fetchData(); + } + + if (CONFIG.localsearch.trigger === 'auto') { + input.addEventListener('input', inputEventFunction); + } else { + document.querySelector('.search-icon').addEventListener('click', inputEventFunction); + input.addEventListener('keypress', event => { + if (event.key === 'Enter') { + inputEventFunction(); + } + }); + } + window.addEventListener('search:loaded', inputEventFunction); + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.classList.add('search-active'); + // Wait for search-popup animation to complete + setTimeout(() => input.focus(), 500); + if (!localSearch.isfetched) localSearch.fetchData(); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + document.addEventListener('pjax:success', () => { + localSearch.highlightSearchWords(document.querySelector('.post-body')); + onPopupClose(); + }); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); diff --git a/js/third-party/statistics/firestore.js b/js/third-party/statistics/firestore.js new file mode 100644 index 0000000..b06dbb1 --- /dev/null +++ b/js/third-party/statistics/firestore.js @@ -0,0 +1,64 @@ +/* global CONFIG, firebase */ + +firebase.initializeApp({ + apiKey : CONFIG.firestore.apiKey, + projectId: CONFIG.firestore.projectId +}); + +(function() { + const getCount = (doc, increaseCount) => { + // IncreaseCount will be false when not in article page + return doc.get().then(d => { + // Has no data, initialize count + let count = d.exists ? d.data().count : 0; + // If first view this article + if (increaseCount) { + // Increase count + count++; + doc.set({ + count + }); + } + return count; + }); + }; + + const appendCountTo = el => { + return count => { + el.innerText = count; + }; + }; + + const db = firebase.firestore(); + const articles = db.collection(CONFIG.firestore.collection); + + document.addEventListener('page:loaded', () => { + + if (CONFIG.page.isPost) { + // Fix issue #118 + // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent + const title = document.querySelector('.post-title').textContent.trim(); + const doc = articles.doc(title); + let increaseCount = CONFIG.hostname === location.hostname; + if (localStorage.getItem(title)) { + increaseCount = false; + } else { + // Mark as visited + localStorage.setItem(title, true); + } + getCount(doc, increaseCount).then(appendCountTo(document.querySelector('.firestore-visitors-count'))); + } else if (CONFIG.page.isHome) { + const promises = [...document.querySelectorAll('.post-title')].map(element => { + const title = element.textContent.trim(); + const doc = articles.doc(title); + return getCount(doc); + }); + Promise.all(promises).then(counts => { + const metas = document.querySelectorAll('.firestore-visitors-count'); + counts.forEach((val, idx) => { + appendCountTo(metas[idx])(val); + }); + }); + } + }); +})(); diff --git a/js/third-party/statistics/lean-analytics.js b/js/third-party/statistics/lean-analytics.js new file mode 100644 index 0000000..8397112 --- /dev/null +++ b/js/third-party/statistics/lean-analytics.js @@ -0,0 +1,107 @@ +/* global CONFIG */ +/* eslint-disable no-console */ + +(function() { + const leancloudSelector = url => { + url = encodeURI(url); + return document.getElementById(url).querySelector('.leancloud-visitors-count'); + }; + + const addCount = Counter => { + const visitors = document.querySelector('.leancloud_visitors'); + const url = decodeURI(visitors.id); + const title = visitors.dataset.flagTitle; + + Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url }))}`) + .then(response => response.json()) + .then(({ results }) => { + if (results.length > 0) { + const counter = results[0]; + leancloudSelector(url).innerText = counter.time + 1; + Counter('put', '/classes/Counter/' + counter.objectId, { + time: { + '__op' : 'Increment', + 'amount': 1 + } + }) + .catch(error => { + console.error('Failed to save visitor count', error); + }); + } else if (CONFIG.leancloud_visitors.security) { + leancloudSelector(url).innerText = 'Counter not initialized! More info at console err msg.'; + console.error('ATTENTION! LeanCloud counter has security bug, see how to solve it here: https://github.com/theme-next/hexo-leancloud-counter-security. \n However, you can still use LeanCloud without security, by setting `security` option to `false`.'); + } else { + Counter('post', '/classes/Counter', { title, url, time: 1 }) + .then(response => response.json()) + .then(() => { + leancloudSelector(url).innerText = 1; + }) + .catch(error => { + console.error('Failed to create', error); + }); + } + }) + .catch(error => { + console.error('LeanCloud Counter Error', error); + }); + }; + + const showTime = Counter => { + const visitors = document.querySelectorAll('.leancloud_visitors'); + const entries = [...visitors].map(element => { + return decodeURI(element.id); + }); + + Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url: { '$in': entries } }))}`) + .then(response => response.json()) + .then(({ results }) => { + for (const url of entries) { + const target = results.find(item => item.url === url); + leancloudSelector(url).innerText = target ? target.time : 0; + } + }) + .catch(error => { + console.error('LeanCloud Counter Error', error); + }); + }; + + const { app_id, app_key, server_url } = CONFIG.leancloud_visitors; + const fetchData = api_server => { + const Counter = (method, url, data) => { + return fetch(`${api_server}/1.1${url}`, { + method, + headers: { + 'X-LC-Id' : app_id, + 'X-LC-Key' : app_key, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + }; + if (CONFIG.page.isPost) { + if (CONFIG.hostname !== location.hostname) return; + addCount(Counter); + } else if (document.querySelectorAll('.post-title-link').length >= 1) { + showTime(Counter); + } + }; + + let api_server; + if (server_url) { + api_server = server_url; + } else if (app_id.slice(-9) === '-MdYXbMMI') { + api_server = `https://${app_id.slice(0, 8).toLowerCase()}.api.lncldglobal.com`; + } + + document.addEventListener('page:loaded', () => { + if (api_server) { + fetchData(api_server); + } else { + fetch(`https://app-router.leancloud.cn/2/route?appId=${app_id}`) + .then(response => response.json()) + .then(({ api_server }) => { + fetchData(`https://${api_server}`); + }); + } + }); +})(); diff --git a/js/third-party/tags/mermaid.js b/js/third-party/tags/mermaid.js new file mode 100644 index 0000000..9623dc5 --- /dev/null +++ b/js/third-party/tags/mermaid.js @@ -0,0 +1,32 @@ +/* global NexT, CONFIG, mermaid */ + +document.addEventListener('page:loaded', () => { + const mermaidElements = document.querySelectorAll('.mermaid'); + if (mermaidElements.length) { + NexT.utils.getScript(CONFIG.mermaid.js, { + condition: window.mermaid + }).then(() => { + mermaidElements.forEach(element => { + const newElement = document.createElement('div'); + newElement.innerHTML = element.innerHTML; + newElement.className = element.className; + const parent = element.parentNode; + // Fix issue #347 + // Support mermaid inside backtick code block + if (parent.matches('pre')) { + parent.parentNode.replaceChild(newElement, parent); + } else { + parent.replaceChild(newElement, element); + } + }); + mermaid.initialize({ + theme : CONFIG.darkmode && window.matchMedia('(prefers-color-scheme: dark)').matches ? CONFIG.mermaid.theme.dark : CONFIG.mermaid.theme.light, + logLevel : 4, + flowchart: { curve: 'linear' }, + gantt : { axisFormat: '%m/%d/%Y' }, + sequence : { actorMargin: 50 } + }); + mermaid.init(); + }); + } +}); diff --git a/js/third-party/tags/pdf.js b/js/third-party/tags/pdf.js new file mode 100644 index 0000000..7e82891 --- /dev/null +++ b/js/third-party/tags/pdf.js @@ -0,0 +1,23 @@ +/* global NexT, CONFIG, PDFObject */ + +document.addEventListener('page:loaded', () => { + if (document.querySelectorAll('.pdf-container').length) { + NexT.utils.getScript(CONFIG.pdf.object_url, { + condition: window.PDFObject + }).then(() => { + document.querySelectorAll('.pdf-container').forEach(element => { + PDFObject.embed(element.dataset.target, element, { + pdfOpenParams: { + navpanes : 0, + toolbar : 0, + statusbar: 0, + pagemode : 'thumbs', + view : 'FitH' + }, + PDFJS_URL: CONFIG.pdf.url, + height : element.dataset.height + }); + }); + }); + } +}); diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000..9920825 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,401 @@ +/* global NexT, CONFIG */ + +HTMLElement.prototype.wrap = function(wrapper) { + this.parentNode.insertBefore(wrapper, this); + this.parentNode.removeChild(this); + wrapper.appendChild(this); +}; + +(function() { + const onPageLoaded = () => document.dispatchEvent( + new Event('page:loaded', { + bubbles: true + }) + ); + + if (document.readyState === 'loading') { + document.addEventListener('readystatechange', onPageLoaded, { once: true }); + } else { + onPageLoaded(); + } + document.addEventListener('pjax:success', onPageLoaded); +})(); + +NexT.utils = { + + registerExtURL: function() { + document.querySelectorAll('span.exturl').forEach(element => { + const link = document.createElement('a'); + // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings + link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + link.rel = 'noopener external nofollow noreferrer'; + link.target = '_blank'; + link.className = element.className; + link.title = element.title; + link.innerHTML = element.innerHTML; + element.parentNode.replaceChild(link, element); + }); + }, + + /** + * One-click copy code support. + */ + registerCopyCode: function() { + let figure = document.querySelectorAll('figure.highlight'); + if (figure.length === 0) figure = document.querySelectorAll('pre:not(.mermaid)'); + figure.forEach(element => { + element.querySelectorAll('.code .line span').forEach(span => { + span.classList.forEach(name => { + span.classList.replace(name, `hljs-${name}`); + }); + }); + if (!CONFIG.copycode.enable) return; + let target = element; + if (CONFIG.copycode.style !== 'mac') target = element.querySelector('.table-container') || element; + target.insertAdjacentHTML('beforeend', '
'); + const button = element.querySelector('.copy-btn'); + button.addEventListener('click', () => { + const lines = element.querySelector('.code') || element.querySelector('code'); + const code = lines.innerText; + if (navigator.clipboard) { + // https://caniuse.com/mdn-api_clipboard_writetext + navigator.clipboard.writeText(code).then(() => { + button.querySelector('i').className = 'fa fa-check-circle fa-fw'; + }, () => { + button.querySelector('i').className = 'fa fa-times-circle fa-fw'; + }); + } else { + const ta = document.createElement('textarea'); + ta.style.top = window.scrollY + 'px'; // Prevent page scrolling + ta.style.position = 'absolute'; + ta.style.opacity = '0'; + ta.readOnly = true; + ta.value = code; + document.body.append(ta); + ta.select(); + ta.setSelectionRange(0, code.length); + ta.readOnly = false; + const result = document.execCommand('copy'); + button.querySelector('i').className = result ? 'fa fa-check-circle fa-fw' : 'fa fa-times-circle fa-fw'; + ta.blur(); // For iOS + button.blur(); + document.body.removeChild(ta); + } + }); + element.addEventListener('mouseleave', () => { + setTimeout(() => { + button.querySelector('i').className = 'fa fa-copy fa-fw'; + }, 300); + }); + }); + }, + + wrapTableWithBox: function() { + document.querySelectorAll('table').forEach(element => { + const box = document.createElement('div'); + box.className = 'table-container'; + element.wrap(box); + }); + }, + + registerVideoIframe: function() { + document.querySelectorAll('iframe').forEach(element => { + const supported = [ + 'www.youtube.com', + 'player.vimeo.com', + 'player.youku.com', + 'player.bilibili.com', + 'www.tudou.com' + ].some(host => element.src.includes(host)); + if (supported && !element.parentNode.matches('.video-container')) { + const box = document.createElement('div'); + box.className = 'video-container'; + element.wrap(box); + const width = Number(element.width); + const height = Number(element.height); + if (width && height) { + box.style.paddingTop = (height / width * 100) + '%'; + } + } + }); + }, + + registerScrollPercent: function() { + const backToTop = document.querySelector('.back-to-top'); + const readingProgressBar = document.querySelector('.reading-progress-bar'); + // For init back to top in sidebar if page was scrolled after page refresh. + window.addEventListener('scroll', () => { + if (backToTop || readingProgressBar) { + const contentHeight = document.body.scrollHeight - window.innerHeight; + const scrollPercent = contentHeight > 0 ? Math.min(100 * window.scrollY / contentHeight, 100) : 0; + if (backToTop) { + backToTop.classList.toggle('back-to-top-on', Math.round(scrollPercent) >= 5); + backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%'; + } + if (readingProgressBar) { + readingProgressBar.style.setProperty('--progress', scrollPercent.toFixed(2) + '%'); + } + } + if (!Array.isArray(NexT.utils.sections)) return; + let index = NexT.utils.sections.findIndex(element => { + return element && element.getBoundingClientRect().top > 10; + }); + if (index === -1) { + index = NexT.utils.sections.length - 1; + } else if (index > 0) { + index--; + } + this.activateNavByIndex(index); + }, { passive: true }); + + backToTop && backToTop.addEventListener('click', () => { + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: 0 + }); + }); + }, + + /** + * Tabs tag listener (without twitter bootstrap). + */ + registerTabsTag: function() { + // Binding `nav-tabs` & `tab-content` by real time permalink changing. + document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(element => { + element.addEventListener('click', event => { + event.preventDefault(); + // Prevent selected tab to select again. + if (element.classList.contains('active')) return; + const nav = element.parentNode; + // Add & Remove active class on `nav-tabs` & `tab-content`. + [...nav.children].forEach(target => { + target.classList.toggle('active', target === element); + }); + // https://stackoverflow.com/questions/20306204/using-queryselector-with-ids-that-are-numbers + const tActive = document.getElementById(element.querySelector('a').getAttribute('href').replace('#', '')); + [...tActive.parentNode.children].forEach(target => { + target.classList.toggle('active', target === tActive); + }); + // Trigger event + tActive.dispatchEvent(new Event('tabs:click', { + bubbles: true + })); + if (!CONFIG.stickytabs) return; + const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10; + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: offset + }); + }); + }); + + window.dispatchEvent(new Event('tabs:register')); + }, + + registerCanIUseTag: function() { + // Get responsive height passed from iframe. + window.addEventListener('message', ({ data }) => { + if (typeof data === 'string' && data.includes('ciu_embed')) { + const featureID = data.split(':')[1]; + const height = data.split(':')[2]; + document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + 'px'; + } + }, false); + }, + + registerActiveMenuItem: function() { + document.querySelectorAll('.menu-item a[href]').forEach(target => { + const isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', ''); + const isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname); + target.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath)); + }); + }, + + registerLangSelect: function() { + const selects = document.querySelectorAll('.lang-select'); + selects.forEach(sel => { + sel.value = CONFIG.page.lang; + sel.addEventListener('change', () => { + const target = sel.options[sel.selectedIndex]; + document.querySelectorAll('.lang-select-label span').forEach(span => { + span.innerText = target.text; + }); + // Disable Pjax to force refresh translation of menu item + window.location.href = target.dataset.href; + }); + }); + }, + + registerSidebarTOC: function() { + this.sections = [...document.querySelectorAll('.post-toc li a.nav-link')].map(element => { + const target = document.getElementById(decodeURI(element.getAttribute('href')).replace('#', '')); + // TOC item animation navigate. + element.addEventListener('click', event => { + event.preventDefault(); + const offset = target.getBoundingClientRect().top + window.scrollY; + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: offset, + complete : () => { + history.pushState(null, document.title, element.href); + } + }); + }); + return target; + }); + }, + + registerPostReward: function() { + const button = document.querySelector('.reward-container button'); + if (!button) return; + button.addEventListener('click', () => { + document.querySelector('.post-reward').classList.toggle('active'); + }); + }, + + activateNavByIndex: function(index) { + const target = document.querySelectorAll('.post-toc li a.nav-link')[index]; + if (!target || target.classList.contains('active-current')) return; + + document.querySelectorAll('.post-toc .active').forEach(element => { + element.classList.remove('active', 'active-current'); + }); + target.classList.add('active', 'active-current'); + let parent = target.parentNode; + while (!parent.matches('.post-toc')) { + if (parent.matches('li')) parent.classList.add('active'); + parent = parent.parentNode; + } + // Scrolling to center active TOC element if TOC content is taller then viewport. + const tocElement = document.querySelector('.sidebar-panel-container'); + if (!tocElement.parentNode.classList.contains('sidebar-toc-active')) return; + window.anime({ + targets : tocElement, + duration : 200, + easing : 'linear', + scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top + }); + }, + + updateSidebarPosition: function() { + if (window.innerWidth < 992 || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return; + // Expand sidebar on post detail page by default, when post has a toc. + const hasTOC = document.querySelector('.post-toc'); + let display = CONFIG.page.sidebar; + if (typeof display !== 'boolean') { + // There's no definition sidebar in the page front-matter. + display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC); + } + if (display) { + window.dispatchEvent(new Event('sidebar:show')); + } + }, + + activateSidebarPanel: function(index) { + const duration = 200; + const sidebar = document.querySelector('.sidebar-inner'); + const panel = document.querySelector('.sidebar-panel-container'); + const activeClassName = ['sidebar-toc-active', 'sidebar-overview-active']; + + if (sidebar.classList.contains(activeClassName[index])) return; + + window.anime({ + duration, + targets : panel, + easing : 'linear', + opacity : 0, + translateY: [0, -20], + complete : () => { + // Prevent adding TOC to Overview if Overview was selected when close & open sidebar. + sidebar.classList.replace(activeClassName[1 - index], activeClassName[index]); + window.anime({ + duration, + targets : panel, + easing : 'linear', + opacity : [0, 1], + translateY: [-20, 0] + }); + } + }); + }, + + getScript: function(src, options = {}, legacyCondition) { + if (typeof options === 'function') { + return this.getScript(src, { + condition: legacyCondition + }).then(options); + } + const { + condition = false, + attributes: { + id = '', + async = false, + defer = false, + crossOrigin = '', + dataset = {}, + ...otherAttributes + } = {}, + parentNode = null + } = options; + return new Promise((resolve, reject) => { + if (condition) { + resolve(); + } else { + const script = document.createElement('script'); + + if (id) script.id = id; + if (crossOrigin) script.crossOrigin = crossOrigin; + script.async = async; + script.defer = defer; + Object.assign(script.dataset, dataset); + Object.entries(otherAttributes).forEach(([name, value]) => { + script.setAttribute(name, String(value)); + }); + + script.onload = resolve; + script.onerror = reject; + + if (typeof src === 'object') { + const { url, integrity } = src; + script.src = url; + if (integrity) { + script.integrity = integrity; + script.crossOrigin = 'anonymous'; + } + } else { + script.src = src; + } + (parentNode || document.head).appendChild(script); + } + }); + }, + + loadComments: function(selector, legacyCallback) { + if (legacyCallback) { + return this.loadComments(selector).then(legacyCallback); + } + return new Promise(resolve => { + const element = document.querySelector(selector); + if (!CONFIG.comments.lazyload || !element) { + resolve(); + return; + } + const intersectionObserver = new IntersectionObserver((entries, observer) => { + const entry = entries[0]; + if (!entry.isIntersecting) return; + + resolve(); + observer.disconnect(); + }); + intersectionObserver.observe(element); + }); + } +}; diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 0000000..d22a432 --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

The Node.js, Hexo and Next themes used in previous blogs are +outdated, causing a lot of compatibility issues. So I reconfigured my +blog and this post records my configuration and development process.

+ +
+ + Read more » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/4152bed9/index.html b/post/4152bed9/index.html new file mode 100644 index 0000000..3731409 --- /dev/null +++ b/post/4152bed9/index.html @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Solve the problem that TensorFlow cannot find the GPU in HPCC Systems | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Solve the problem that TensorFlow cannot find the GPU in HPCC Systems +

+ + +
+ + + + +
+

According to the configuration instructions provided by TensorFlow +and CUDA, I installed and configured the environment, but HPCC Systems +couldn't detect the GPU. This blog post documents the process of how I +resolved this issue.

+ +

I run the test code:

+
1
2
3
4
5
6
7
8
9
10
11
12
IMPORT PYTHON3 AS PYTHON;

STRING GPUtest() := EMBED(Python)
import tensorflow as tf
if tf.test.is_gpu_available():
return 'available'
else:
return 'unavailable'
ENDEMBED;

res := GPUtest();
OUTPUT(res, NAMED('res'))
+

Reason : In my previous environment setup, I installed TensorFlow and +CUDA in root mode, but I only configured the environment information in +the .bashrc file of the current user. +However, HPCC Systems creates a new user named "hpcc" and uses the +environment variables from that user. As a result, in the "hpcc" user, +the LD_LIBRARY_PATH and other environment variables were +not present, causing CUDA and GPU recognition to fail.

+

I first modify the password of hpcc:

+
1
sudo passwd hpcc
+

In Ubuntu, there are two methods to switch to another user:

+
    +
  • su user: The su command requires you to enter the password of the +target user. You must know the password of the target user and have root +user privileges. When switching to the target user using the su command, +the target user's complete environment variables are not loaded. It only +switches to the target user's identity and inherits the current user's +environment variables.
  • +
  • sudo -i -u user: When using the sudo -i -u user command to switch to +the target user, the target user's complete environment variables are +loaded. It switches you to the target user's identity and loads the +target user's environment settings.
  • +
+

According to the environment variable setting rules in Linux, I have +added the previously set environment variables into +/etc/profile:

+
1
2
3
4
5
6
7
8
alias python='python3'

export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/cuda-11.8/lib64
export PATH=/usr/local/cuda-11.8/bin${PATH:+:${PATH}}

CUDNN_PATH=$(dirname $(python -c "import nvidia.cudnn;print(nvidia.cudnn.__file__)"))
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CUDNN_PATH/lib

+

Using sudo -i -u hpcc to enter the hpcc user and typing +env, I found that the previous settings have taken effect. +Please note that at this point, you should not use su hpcc +to enter the user, as it would load incorrect environment variables.

+

However, even after making these settings, it appears that HPCC still +cannot properly recognize the GPU.

+

So I tried running the code in HPCC, retrieving the environment +variables, and restarting HPCC Systems. After that, I ran:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IMPORT PYTHON3 AS PYTHON;

STRING GPUtest() := EMBED(Python)
import tensorflow as tf
import subprocess

command = "env"
result = subprocess.run(command, shell=True, capture_output=True, text=True)
exit_code = result.returncode
output = result.stdout
error = result.stderr
return output
ENDEMBED;

res := GPUtest();
OUTPUT(res, NAMED('res'))
+

I found that the environment variable LD_LIBRARY_PATH +was not loaded correctly. Could it be because the +CUDNN_PATH is using a Python statement that was not +executed correctly? To test this, I changed CUDNN_PATH +to

+

1
/usr/local/lib/python3.10/dist-packages/nvidia/cudnn/lib

+

which is the value obtained from the terminal, and after testing, I +found that the GPU could be recognized correctly.

+

Additionally, I later stumbled upon an error:

+
1
2
3
/etc/profile: line 33: python: command not found
dirname: missing operand
Try 'dirname --help' for more information.
+

It turned out that the python command was not recognized +correctly. I resolved this issue by changing python to +python3 in the following setting:

+
1
CUDNN_PATH=$(dirname $(python3 -c "import nvidia.cudnn;print(nvidia.cudnn.__file__)"))
+

After making this change, the code ran successfully without any +issues.

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/4a752ce/image-20230619044834916.png b/post/4a752ce/image-20230619044834916.png new file mode 100644 index 0000000..b57914b Binary files /dev/null and b/post/4a752ce/image-20230619044834916.png differ diff --git a/post/4a752ce/index.html b/post/4a752ce/index.html new file mode 100644 index 0000000..3fd7cc9 --- /dev/null +++ b/post/4a752ce/index.html @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Debug ECL programming | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Debug ECL programming +

+ + +
+ + + + +
+

In the ECL language, operations such as OUTPUT cannot be performed in +the definition, complicating our view of intermediate variables. This +blog introduces debugging tips for several ECL programming language.

+ +

Use WHEN to output +intermediate variables

+

Unlike programming languages such as C++ and Python, in ECL, the +OUTPUT operation cannot be performed in the function +definition. It will prompt

+
+

WHEN must be used to associate an action with a definition

+
+

We can use WHEN to output intermediate variables. For +example:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//a FUNCTION with side-effect Action
namesTable := FUNCTION
namesRecord := RECORD
STRING20 surname;
STRING10 forename;
INTEGER2 age := 25;
END;
o := OUTPUT('namesTable used by user <x>');
ds := DATASET([{'x','y',22}],namesRecord);
RETURN WHEN(ds,O);
END;

z := namesTable();
OUTPUT(z);
+

For multiple outputs, SEQUENTIAL can be used. For +example:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
//a FUNCTION with side-effect Action
namesTable := FUNCTION
namesRecord := RECORD
STRING20 surname;
STRING10 forename;
INTEGER2 age := 25;
END;
ds := DATASET([{'x','y',22}],namesRecord);
RETURN WHEN(ds,SEQUENTIAL(OUTPUT('namesTable used by user <x>'), OUTPUT('test2')));
END;

z := namesTable();
OUTPUT(z);
+

Logging with Std.System.Log

+

By using Std.System.Log, some logs can be recorded. For +example:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IMPORT Std.System.Log AS Syslog;

//a FUNCTION with side-effect Action
namesTable := FUNCTION
namesRecord := RECORD
STRING20 surname;
STRING10 forename;
INTEGER2 age := 25;
END;
ds := DATASET([{'x','y',22}],namesRecord);
RETURN WHEN(ds, Syslog.addWorkunitInformation('test1234'));
END;

z := namesTable();
OUTPUT(z);
+

In Helpers of ECL Watch, in EclAgentLog, you can find +the corresponding log.

+
+image-20230619044834916 + +
+

In +embedded languages, use assert to check intermediate variables

+

When we need to debug some embedded Python codes, we cannot use the +print function to print intermediate variables and can only +output them externally by means of an +assert False, 'message'. For example, if I want to view the +summary of the model I defined, I can use the following:

+
1
2
3
4
5
# Debug
stringlist = []
mod.summary(print_fn=lambda x: stringlist.append(x))
short_model_summary = "\n".join(stringlist)
assert 1 == 0, short_model_summary
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/4e995520/index.html b/post/4e995520/index.html new file mode 100644 index 0000000..f664aa7 --- /dev/null +++ b/post/4e995520/index.html @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +numpy() is only available when eager execution is enabled in TensorFlow2 and its solution | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ numpy() is only available when eager execution is enabled in TensorFlow2 and its solution +

+ + +
+ + + + +
+

When upgrading the HPCC GNN bundle from TensorFlow1.x to +TensorFlow2.x, I encountered an error of +numpy() is only available when eager execution is enabled. +This blog records the cause and solution of the problem.

+ +

Problem Description

+

When writing the test code, I encountered a strange bug.

+

I defined a TensorFlow model:

+
1
2
3
4
5
6
7
8
9
10
11
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import models,layers

tf.keras.backend.clear_session()

model = models.Sequential()
model.add(layers.Dense(20,activation = 'relu',input_shape=(15,)))
model.add(layers.Dense(10,activation = 'relu' ))
model.add(layers.Dense(1,activation = 'sigmoid' ))

+

When defining the optimizer, if the name is used directly, it can be +compiled normally. The code is as follows:

+
1
2
3
model.compile(optimizer=‘Adam’,
loss='binary_crossentropy',
metrics=['AUC'])
+

But if I use

+
1
2
3
model.compile(optimizer=tf.keras.optimizers.Adam(),
loss='binary_crossentropy',
metrics=['AUC'])
+

will report an error:

+
+

numpy() is only available when eager execution is enabled.

+
+

Cause and solution

+

After many investigations, it is found that the result of +tf.executing_eagerly() is False. This is strange. I am +using TensorFlow2.12, and the eager mode is enabled by +default. Why does this error still occur?

+

The reason is that I upgraded from the compatibility mode of +TensorFlow2. That is, the original TensorFlow mode is:

+
1
2
import tensorflow.compat.v1 as tf # V2.x
tf.disable_v2_behavior()
+

Before the HPCC System Platform is restarted, +tf.disable_v2_behavior() will always take effect, so the +value of tf.executing_eagerly() is False.

+

Solution: restart the HPCC System Platform.

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/57ba8585/index.html b/post/57ba8585/index.html new file mode 100644 index 0000000..ffa63fc --- /dev/null +++ b/post/57ba8585/index.html @@ -0,0 +1,587 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Learning ECL Programming language | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Learning ECL Programming language +

+ + +
+ + + + +
+

Learn the basics of ECL, the powerful programming language built for +big data analytics.

+ +

File type

+

There are two kinds of operations in ECL, definition (Definitions) +and execution (Actions). After using EXPORT to execute the definition +operation, it will no longer be able to perform operations in this +file.

+

Similarly, ECL has two kinds of files. Their suffixes are +.ecl. Definitions file and build execution (BWR, Builder +Window Runnable) file. The difference is

+
    +
  1. The definition file always contains EXPORT and SHARED definitions +and contains no execution operations. Therefore, the file cannot be +executed through Submit.
  2. +
  3. The BWR file contains at least one execution operation and no EXPORT +and SHARED definition operations.
  4. +
+

Define variables

+

Variable names cannot have spaces and end with a semicolon. Format +for defining variables:

+
1
[Scope] [ValueType] Name [ (parms) ] := Expression [ :WorkflowService] ;
+

For example:

+
1
2
3
4
My_First_Definition1 := 5; // valid name
My First Definition := 5; // INVALID name, spaces not allowed
MyFirstDefinition := 5; //defined as 5
MySecondDefinition := MyFirstDefinition + 5; //this is 10
+

Variable name cannot start with +UNICODE_ , UTF8_, VARUNICODE_

+

Basic variable type

+

Boolean

+

Boolean can be defined by expression, TRUE or +FALSE, for example:

+
1
2
3
IsBoolTrue  := TRUE;
IsFloridian := Person.per_st = 'FL';
IsOldPerson := Person.Age >= 65;
+

Value

+

Value can be defined by an expression, and the result must be an +arithmetic value or a string:

+
1
2
3
ValueTrue      := 1;
FloridianCount := COUNT(Person(Person.per_st = 'FL'));
OldAgeSum := SUM(Person(Person.Age >= 65),Person.Age);
+

INTEGER

+
1
2
[IntType] [UNSIGNED] INTEGER[n]
[IntType] UNSIGNEDn
+

Among them, n indicates that this integer occupies number of +bytes, which can be 1~8. The default is 8.

+

IntType describes whether the high bit of the number is at +the low address or the low bit is at the low address. Can take either +BIG_ENDIAN or LITTLE_ENDIAN. Default is +LITTLE_ENDIAN.

+

UNSIGNED, used to describe whether it is signed or not, the default +is signed.

+

REAL[** n **] represents a floating point +number, n can be 4 (7 significant figures) or 8 (15 significant +figures)

+

Set

+

All elements must be of the same type.

+

Example:

+
1
2
3
SetInts  := [1,2,3,4,5]; // an INTEGER set with 5 elements
SetExp := [1,2+3,45,SomeIntegerDefinition,7*3];
SetSomeField := SET(SomeFile, SomeField);
+

SET can be accessed by subscript, subscript starts from +1

+
1
2
3
MySet := [5,4,3,2,1];
ReverseNum := MySet[2]; //indexing to MySet's element number 2,
//so ReverseNum contains the value 4
+

Strings are treated as SET with multiple 1-character elements, so +they can also be accessed by subscript

+
1
2
MyString := 'ABCDE';
MySubString := MyString[2]; // MySubString is 'B'
+

Strings support range access:

+
1
2
3
4
MyString := 'ABCDE';
MySubString1 := MyString[2..4]; // MySubString1 is 'BCD'
MySubString2 := MyString[ ..4]; // MySubString2 is 'ABCD'
MySubString3 := MyString[2.. ]; // MySubString3 is 'BCDE'
+

The data type in the Set can be specified:

+
1
2
3
4
5
6
SET OF INTEGER1 SetValues := [5,10,15,20];

IsInSetFunction(SET OF INTEGER1 x=SetValues,y) := y IN x;

OUTPUT(IsInSetFunction([1,2,3,4],5)); //false
OUTPUT(IsInSetFunction(,5)); // true
+

Keywords

+

EXPORT

+

How to use: EXPORT [ VIRTUAL ] +definition, only one EXPORT Module is allowed +in each file, and the name of this Module must be the +same as the file name.

+

VIRTUAL is optional. If specified, the definition is only valid +within the Module. Allows usage as Module.Definition from other +files.

+

EXPORT allows nesting. If you want to access a value in Module from +another file, this value must also be modified by EXPORT.

+

Example, file1:

+
1
2
3
4
EXPORT file1 := MODULE
Value1 := 5;
EXPORT Value2 :=6;
END;
+

file2:

+
1
2
3
4
5
IMPORT MyTest;
myVar := MyTest.Value2;
//myVar := MyTest.Value1; // 报错,Value1不可见
OUTPUT(myVar); //输出6

+

Data structure

+

ENUM

+

Enums can be useful when you want to represent a limited set of +possible values for a variable or a parameter. For example:

+
1
2
3
4
Color := ENUM(RED=1, GREEN=2, BLUE=3);
myColor := Color.RED;

OUTPUT(myColor); # 1
+

RECORD

+

A RECORD in ECL represents the structure or format of a +dataset. It is similar to the concept of a "table" in a SQL database, +where each field in the record is similar to a column in the table. It +defines the data types and names of fields.

+

For example:

+
1
2
3
4
5
6
7
8
9
10
11
ChildRec := RECORD
UNSIGNED4 person_id;
STRING20 per_surname;
STRING20 per_forename;
END;

rec := RECORD
STRING20 name;
INTEGER4 age;
BOOLEAN isEmployed;
END;
+

usually used with DATASET.

+

DATASET

+

It represents a set of data. A dataset is a group of records with the +same record layout. A record layout is defined using the +RECORD structure, which contains a set of fields, each with +a name and a type.

+

How to use:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
attr := DATASET( file, struct, filetype [,LOOKUP]);

attr := DATASET( dataset, file, filetype [,LOOKUP]);

attr := DATASET( WORKUNIT( [ wuid , ] namedoutput ), struct );

[ attr := ] DATASET( recordset [, recstruct ] );

DATASET( row )

DATASET( childstruct [, COUNT( count ) | LENGTH( size ) ] [, CHOOSEN( maxrecs ) ] )

[GROUPED] [LINKCOUNTED] [STREAMED] DATASET( struct )

DATASET( dict )

DATASET( count, transform [, DISTRIBUTED | LOCAL ] )
+

Example:

+
1
2
3
4
5
6
7
8
9
rec := RECORD
STRING20 name;
INTEGER4 age;
BOOLEAN isEmployed;
END;

myData := DATASET([{ 'John', 30, TRUE }, { 'Jane', 25, FALSE }], rec);

OUTPUT(myData)
+

Construct the dataset:

+

Use +DATASET( count, transform [, DISTRIBUTED | LOCAL ] ), for +example:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
RAND_MAX := POWER(2,32) -1;
trainCount := 1000;
featureCount := 5;
trainRec := RECORD
UNSIGNED8 id;
SET OF REAL x;
REAL4 y;
END;

REAL4 targetFunc(REAL4 x1, REAL4 x2, REAL4 x3, REAL4 x4, REAL4 x5) := FUNCTION
rslt := .5 * POWER(x1, 4) - .4 * POWER(x2, 3) + .3 * POWER(x3,2) - .2 * x4 + .1 * x5;
RETURN rslt;
END;

train0 := DATASET(trainCount, TRANSFORM(trainRec,
SELF.id := COUNTER,
SELF.x := [(RANDOM() % RAND_MAX) / RAND_MAX -.5,
(RANDOM() % RAND_MAX) / RAND_MAX -.5,
(RANDOM() % RAND_MAX) / RAND_MAX -.5,
(RANDOM() % RAND_MAX) / RAND_MAX -.5,
(RANDOM() % RAND_MAX) / RAND_MAX -.5],
SELF.y := targetFunc(SELF.x[1], SELF.x[2], SELF.x[3], SELF.x[4], SELF.x[5]))
);
OUTPUT(train0, NAMED('trainData'));

// select the first record
record1 := CHOOSEN(train0, 1);

// extract the x field
xField := PROJECT(record1, TRANSFORM({SET OF REAL x}, SELF.x := LEFT.x));

// output x field
OUTPUT(xField, NAMED('xFieldOfRecord1'));

+

Built-in functions and +operations

+

OUTPUT

+

This function is for output values.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[attr := ] OUTPUT(recordset [, [ format ] [,file [thorfileoptions ] ] [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );

[attr := ] OUTPUT(recordset, [ format ] ,file , CSV [ (csvoptions) ] [csvfileoptions ] [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );

[attr := ] OUTPUT(recordset, [ format ] , file , XML [ (xmloptions) ] [xmlfileoptions ] [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );

[attr := ] OUTPUT(recordset, [ format ] , file , JSON [ (jsonoptions) ] [jsonfileoptions ] [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );

[attr := ] OUTPUT(recordset, [ format ] ,PIPE( pipeoptions [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );

[attr := ] OUTPUT(recordset [, format ] , NAMED( name ) [,EXTEND] [,ALL] [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );

[attr := ] OUTPUT( expression [, NAMED( name ) ] [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );

[attr := ] OUTPUT( recordset , THOR [, NOXPATH ] [, UNORDERED | ORDERED( bool ) ] [, STABLE | UNSTABLE ] [, PARALLEL [ ( numthreads ) ] ] [, ALGORITHM( name ) ] );
+

For example, to output 111 to the test panel, you can write:

+
1
OUTPUT(1111, NAMED('test'));
+

TABLE

+

Used to create a new dataset (Dataset). The TABLE +function takes a set of records (each defined by a RECORD +structure) and an optional filter condition, and returns a new +dataset.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
person := RECORD
STRING20 name;
UNSIGNED1 age;
END;

people := DATASET
(
[
{'John', 30 },
{'Jane', 25 },
{'Tom', 35 }
],
person
);

adults := TABLE
(
people,
{
STRING20 name := name;
UNSIGNED1 age := age;
BOOLEAN isAdult := age >= 18;
}
);

OUTPUT(adults);

+

PROJECT

+

Execute the TRANSFORM operation on each item in the DATASET.

+

TRANSFORM

+

Convert one DATASET to another DATASET according to the rules.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
IMPORT STD;

Layout_Person := RECORD
STRING1 num;
STRING4 name;
END;

Layout_Output := RECORD
Layout_Person;
STRING4 upper_name;
END;

ds_Persons := DATASET([{'1', 'one'}, {'2', 'two'}, {'3', 'three'}], Layout_Person);

ds_Output := PROJECT(ds_Persons,
TRANSFORM(Layout_Output,
SELF.num := LEFT.num,
SELF.name := LEFT.name,
SELF.upper_name := STD.Str.ToUpperCase(LEFT.name)));

OUTPUT(ds_Output);

+

result:

+
1
2
3
4
1	one 	ONE 
2 two TWO
3 thre THRE

+

NORMALIZE

+

In PROJECT, the TRANSFORM operation is performed on each piece of +data, and then a new data set is obtained, which is one-to-one in +quantity. And NORMALIZE is to expand a single piece of data into +multiple pieces of data. In some cases, you may have a field that +contains repeated data, and you may wish to split each repetition into a +separate record. In this case, you can use the NORMALIZE +function. The NORMALIZE function is used by receiving a +dataset and a TRANSFORM function and returning a new dataset. In the +conversion function, you need to define how to split the original +records into new records.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Layout := RECORD
STRING10 name;
INTEGER4 times;
END;

ds := DATASET([{'John', 3}, {'Jane', 2}], Layout);

Layout_Output := RECORD
STRING10 name;
END;

ds_Output := NORMALIZE(ds, LEFT.times,
TRANSFORM(Layout_Output,
SELF.name := LEFT.name));

OUTPUT(ds_Output);

+

ds.times indicates the number of times to repeat, and +the TRANSFORM function defines how to convert the original +record into a new record. In the output dataset, John and +Jane will appear 3 and 2 times, respectively. Note that in +the NORMALIZE function, for the current data, use LEFT +reference.

+

JOIN

+

Merge the two DATASETs.

+

JOIN( leftrecset, rightrecset, +joincondition [** , transform **] [** , +jointype **] [** , joinflags **] )

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Layout := RECORD
STRING1 id;
STRING10 name;
END;

Layout2 := RECORD
STRING1 id;
STRING10 job;
END;

OutputLayout := RECORD
STRING1 id;
STRING20 name;
END;

ds1 := DATASET([{'1', 'John'}, {'2', 'Jane'}], Layout);
ds2 := DATASET([{'1', 'Engineer'}, {'2', 'Doctor'}], Layout2);

ds_join := JOIN(ds1, ds2, LEFT.id = RIGHT.id,
TRANSFORM(OutputLayout,
SELF.id := LEFT.id,
SELF.name := (LEFT.name + ', ' + RIGHT.job)));

OUTPUT(ds_join);

+

EMBED

+

Embedding code in other languages.

+
1
2
3
4
EMBED(language)
embed code.....
ENDEMBED;

+

Example:

+
1
2
3
4
5
6
7
8
IMPORT Python3 AS Python;

INTEGER4 MySquare(INTEGER4 val) := EMBED(Python)
return val * val
ENDEMBED;

OUTPUT(MySquare(5)); // 25

+

LOCAL

+

The LOCAL keyword is mainly used to limit the scope of +data or functions, or to control how data is distributed in the +cluster.

+

If a definition (for example, a dataset or function) is declared +LOCAL, then this definition is only visible in the ECL +statement in which it is declared. This is similar to local variables in +other programming languages. For example:

+
1
2
3
4
ECLCopy codemyFunction := FUNCTION
LOCAL myLocalValue := 5; // Only available in this function
RETURN myLocalValue * 2;
END;
+

Additionally, when the LOCAL keyword is used on a +dataset, it indicates that the dataset should be computed locally on +each node, rather than across the entire cluster. This can be useful for +reducing network communication and speeding up calculations. For +example, the following ECL statement creates a dataset that is computed +locally on each node:

+
1
2
ECLCopy code
myLocalDataset := DATASET([{1, 'one'}, {2, 'two'}, {3, 'three'}], myRecordDef, LOCAL);
+

In this example, myRecordDef is a record definition that +describes the structure of each record in the dataset. Each node will +process a portion of this dataset, not the entire dataset.

+

ASSERT

+

It is often used to judge whether a function has obtained the +expected result.

+

ASSERT( condition [** , +message **] [** , **FAIL ] [** , **CONST ])

+

ASSERT( recset, condition [** , +message **] [** , **FAIL ] [** , **CONST ] [, UNORDERED | +ORDERED(** bool **) ] [, STABLE | UNSTABLE ] [, PARALLEL [ (** +numthreads **) ] ] [, ALGORITHM(** name **) ] +)

+

ASSERT( recset, assertlist [, +UNORDERED | ORDERED(** bool **) ] [, STABLE | UNSTABLE ] [, +PARALLEL [ (** numthreads **) ] ] [, ALGORITHM(** name +**) ] )

+

recset can be a DATASET, and ASSERT judges the results one by +one.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
IMPORT Python3 AS Python;
IMPORT Std.System.Thorlib;

nNodes := Thorlib.nodes();
nodeId := Thorlib.node();
testRec := RECORD
UNSIGNED node;
STRING tf_version;
STRING result := '';
END;
DATASET(testRec) DoTest := FUNCTION
DATASET(testRec) testTF(DATASET(testRec) test) := EMBED(Python: activity)
import traceback as tb
nodeId = 999
v = 0
try:
for tr in test:
# Should only be one.
nodeId, unused, result = tr
try:
import tensorflow.compat.v1 as tf # V2.x
tf.disable_v2_behavior()
except:
import tensorflow as tf # V 1.x
s = tf.Session()
v = tf.__version__
return [(nodeId, v, 'SUCCESS')]
except:
return [(nodeId, 'unknown', tb.format_exc())]
ENDEMBED;
inp := DATASET([{nodeId, '', ''}], testRec, LOCAL);
outp := testTF(inp);
RETURN ASSERT(outp, result = 'SUCCESS', 'Error on node ' + node + ': ' + result);
END;

OUTPUT(DoTest, NAMED('Result'));
+

SORT

+

The SORT function is used to sort the records in the +data set (DATASET) according to the specified field. Its basic usage +syntax is as follows:

+
1
SortedDataset := SORT(Dataset, Field);
+

Here, Dataset is the dataset you want to sort by, and +Field is the field you want to sort by.

+

A dataset that contains employee information and wants to sort by the +Salary field:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Employee := RECORD
STRING20 Name;
STRING20 Occupation;
UNSIGNED Salary;
END;

Employees := DATASET([
{'John', 'Doctor', 80000},
{'Alice', 'Engineer', 70000},
{'Bob', 'Teacher', 60000}], Employee);

SortedEmployees := SORT(Employees, Salary);

// descending order
SortedEmployees := SORT(Employees, -Salary);
// sort by multiple fields
SortedEmployees := SORT(Employees, Occupation, -Salary);

OUTPUT(SortedEmployees);

+

MAX

+

Get the maximum value:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// create data set
peopleRec := RECORD
STRING5 name;
UNSIGNED2 age;
STRING10 job;
END;

people := DATASET([
{'John', 25, 'Teacher'},
{'Mary', 30, 'Engineer'},
{'Bob', 35, 'Engineer'},
{'Lisa', 40, 'Doctor'},
{'Mike', 28, 'Teacher'},
{'Anna', 32, 'Doctor'}
], peopleRec);

// Group the dataset by the job field and find the maximum age in each group
test := TABLE(people, {job, maxAge := MAX(GROUP, age)}, job);

// Find the maximum age among all people without grouping the dataset by the job field
test2 := TABLE(people, {job, maxAge := MAX(GROUP, age)});

// output result
OUTPUT(test, NAMED('MaxAgePerJob'));
OUTPUT(test2, NAMED('MaxAge'));


+

COUNT

+

Calculate the size of the data.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Layout := RECORD
STRING20 Name;
UNSIGNED2 Age;
STRING20 Occupation;
END;

ds := DATASET([{'John', 30, 'Doctor'},
{'Alice', 25, 'Engineer'},
{'Bob', 35, 'Teacher'}], Layout);

dsCount := COUNT(ds);

OUTPUT(dsCount);

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/753b6e6d/image-20230719123923331.png b/post/753b6e6d/image-20230719123923331.png new file mode 100644 index 0000000..3995ab9 Binary files /dev/null and b/post/753b6e6d/image-20230719123923331.png differ diff --git a/post/753b6e6d/image-20230719123940023.png b/post/753b6e6d/image-20230719123940023.png new file mode 100644 index 0000000..3995ab9 Binary files /dev/null and b/post/753b6e6d/image-20230719123940023.png differ diff --git a/post/753b6e6d/image-20230719123949833.png b/post/753b6e6d/image-20230719123949833.png new file mode 100644 index 0000000..8903f86 Binary files /dev/null and b/post/753b6e6d/image-20230719123949833.png differ diff --git a/post/753b6e6d/image-20230719124004497.png b/post/753b6e6d/image-20230719124004497.png new file mode 100644 index 0000000..894e0ed Binary files /dev/null and b/post/753b6e6d/image-20230719124004497.png differ diff --git a/post/753b6e6d/index.html b/post/753b6e6d/index.html new file mode 100644 index 0000000..f6b9e42 --- /dev/null +++ b/post/753b6e6d/index.html @@ -0,0 +1,406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Upload images to HPCC Systems | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Upload images to HPCC Systems +

+ + +
+ + + + +
+

In HPCC Systems, pictures need to be uploaded to HPCC Systems before +they can be accessed. This blog post documents how to upload an image to +HPCC Systems

+ +

First upload the image to Landing Zones in ECL Watch:

+
+image-20230719123923331 + +
+

After uploading, select this picture, and in Import, select BLOB:

+
+image-20230719123949833 + +
+

Set up as follows:

+
+image-20230719124004497 + +
+

Then use the following code to get the Hex content of this image:

+
1
2
3
4
5
6
7
8
9
imageRecord := RECORD
STRING filename;
DATA image;
//first 4 bytes contain the length of the image data
UNSIGNED8 RecPos{virtual(fileposition)};
END;

imageData := DATASET('~Your::custom::Name',imageRecord,FLAT);
OUTPUT(imageData, NAMED('elephant'));
+

If you want to convert this Hex content into np array for subsequent +processing, you can use the following code:

+
1
2
3
4
5
6
7
8
9
10
STRING hexToNparry(DATA byte_array):= EMBED(Python)
from PIL import Image
import numpy as np
import io
bytes_data = bytes(byte_array)
image = Image.open(io.BytesIO(bytes_data))
I_array = np.array(image) # this is all you need
return ''.join(str(i) for i in I_array.shape) # check the shape of this image, height * width* channel
ENDEMBED;
OUTPUT(hexToNparry(imageData[1].image), NAMED('npShape'));
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/8f9c2317/15c49db55792dcbcf6f29defb40871a7.jpg b/post/8f9c2317/15c49db55792dcbcf6f29defb40871a7.jpg new file mode 100644 index 0000000..6000b40 Binary files /dev/null and b/post/8f9c2317/15c49db55792dcbcf6f29defb40871a7.jpg differ diff --git a/post/8f9c2317/index.html b/post/8f9c2317/index.html new file mode 100644 index 0000000..1689fc0 --- /dev/null +++ b/post/8f9c2317/index.html @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +New Blog, New Beginning | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ New Blog, New Beginning +

+ + +
+ + + + +
+

The Node.js, Hexo and Next themes used in previous blogs are +outdated, causing a lot of compatibility issues. So I reconfigured my +blog and this post records my configuration and development process.

+ +

Install Hexo

+
    +
  1. Install Node.js, use the command node --version to +view the installed version, the version I installed is 16.17.0.

  2. +
  3. Install Hexo using npm install -g hexo-cli.

  4. +
  5. Use the following commands to initialize and complete the most +basic website building.

    +
    1
    2
    3
    $ hexo init <BlogDir>
    $ cd <folder>
    $ npm install
  6. +
+

My Hexo version is 6.3.0. For more detailed commands, please refer to +This Doc.

+

Using Next theme

+

There are currently three versions of the hexo next theme: the earliest +version, the second +version, these two versions have been stopped maintenance, and the +latest +version is still being maintained. Please refer to this +doc for the relationship between the three versions.

+

All subsequent configurations are based on the latest +version.

+
    +
  1. Download and install the Next theme:

    +
    1
    2
    $ cd hexo-site
    $ git clone https://github.com/next-theme/hexo-theme-next themes/next
  2. +
  3. Set theme: next in +Hexo site __config.yml to enable the Next theme.

  4. +
  5. Set scheme: Pisces in +Next Theme __config.yml(You can use other scheme).

  6. +
+

Use abbrlink

+

Hexo uses the Markdown file name as the link by default. If the file +name has Chinese, Hexo need to escape the it. In addition, it may lead +to a very long link . Modifying the article title causes the article +title to not match the file name. Modifying the file name will cause the +article link to be modified. None of these above is helpful for search +engines to search your blog.

+

So I hash the filename and use the hash as the link to the article. +In this way, the article link will never change, which is convenient for +search engines to search.

+

Install hexo-abbrlink:

+
1
$ npm install hexo-abbrlink --save
+

In Hexo site __config.yml set:

+
1
2
3
4
5
6
permalink: /post/:abbrlink/

# abbrlink config
abbrlink:
alg: crc32 # crc16(default) and crc32
rep: hex # dec(default) and hex
+

Please refer to this +doc for more information.

+

Modify blog image link

+

When using hexo-abbrlink, the address in +the default Markdown syntax for adding images +![image description](address/image.jpg) is not converted, +resulting in the image not being displayed. Referring to the previous +hexo-asset-image plugin, I wrote a new plugin to solve this +problem.

+

Install the plugin:

+
1
npm install https://github.com/littlesevenmo/hexo-asset-image2.git --save
+

in the Hexo site __config.yml set:

+
1
2
post_asset_folder: true
root: /
+

In this way, your blog's post structure will look like this:

+
1
2
3
YourBlogDoc
├── image.jpg
YourBlogDoc.md
+

Insert image image.jpg by +![image description](YourBlogDoc/image.jpg) or +![image description](D:/<Absolute_Addr>/YourBlogDoc/image.jpg). +Then use hexo clean, hexo g, +hexo server to preview.

+

Blog appearance modification

+ +

In +<BlogDir>/themes/next/source/css/_common/components/post/post-body.styl, +add:

+
1
2
3
4
5
6
7
8
9
10
.post-body p a{
color: #0593d3;
border-bottom: none;
border-bottom: 1px solid #0593d3;
&:hover {
color: #fc6423;
border-bottom: none;
border-bottom: 1px solid #fc6423;
}
}
+

Delete have XX posts in +total

+

In +<BlogDir>/themes/next/source/css/_common/components/post/post-collapse.styl, +add display: none in .collection-title:

+
1
2
3
.collection-title {
display:none;
....
+

Delete year and date from +archives

+

In +<BlogDir>/themes/next/source/css/_common/components/post/post-collapse.styl, +add display: none in .collection-year:

+
1
2
.collection-year {
display:none;
+

In +<BlogDIr>/themes/next/layout/_macro/post-collapse.njk, +delete:

+
1
2
3
4
5
6
7
<div class="post-meta-container">
<time itemprop="dateCreated"
datetime="{{ moment(post.date).format() }}"
content="{{ date(post.date, config.date_format) }}">
{{ date(post.date, 'MM-DD') }}
</time>
</div>
+

Delete powered by Hexo

+

In Hexo site __config.yml, set:

+
1
2
footer
powered: false
+

Set posts tags from # to tag +icon

+

In Hexo site __config.yml, set +tag_icon: true

+

Set categories, tags, etc.

+
    +
  1. In Next Theme __config.yml, set:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    menu:
    home: / || fa fa-home
    about: /about/ || fa fa-user
    tags: /Tags/ || fa fa-tags
    categories: /Categories/ || fa fa-th
    archives: /archives/ || fa fa-archive
    #schedule: /schedule/ || fa fa-calendar
    #sitemap: /sitemap.xml || fa fa-sitemap
    #commonweal: /404/ || fa fa-heartbeat
  2. +
  3. Create a catalog file:

    +
    1
    2
    cd <BlodDir>
    $ hexo new page categories
    +

    In <BlogDir>/source/categories/index.md, add:

    +
    1
    type: "categories"
    +

    to article header.

  4. +
  5. In this way, the content in the page is Category content. Tag is +the same.

  6. +
  7. Add the follows to the head of the blog post:

    +
    1
    2
    3
    4
    5
    categories: 
    - Your categorie
    tags:
    - Tag1
    - Tag2
    +

    Then you set categories and tags for articles.

  8. +
+

AboutMe content can be written in the About page.

+

Turn off motion

+

In Next Theme __config.yml, set:

+
1
2
motion:
enable: false
+

You can also modify other motion configurations in +motion.

+

Enable article loading +progress bar

+

In Next Theme __config.yml, set:

+
1
2
pace:
enable: true
+

Enable math formulas

+

The syntax of the original math formula rendering plugin is not +compatible with Markdown syntax, so I use pandoc for rendering.

+
    +
  1. Install pandoc.

  2. +
  3. Uninstall the original math formula editing plugin

    +
    1
    $ npm uninstall hexo-renderer-marked
  4. +
  5. Install the plugin using pandoc:

    +
    1
    npm install hexo-renderer-pandoc
  6. +
  7. In Next Theme __config.yml, set the follows to +enable mathjax:

    +
    1
    2
    3
    4
    mathjax:
    enable: true
    # Available values: none | ams | all
    tags: none
  8. +
  9. Add the follows in the head of the blog post you want to render +with Mathjax:

    +
    1
    mathjax: true
  10. +
  11. In Hexo site __config.yml, set:

    +
    1
    2
    pandoc:
    pandoc_path: C:/Program Files/Pandoc/pandoc.exe # Your real program address
  12. +
+

Add search function

+ +
    +
  1. Install the search plugin:

    +
    1
    $ npm install hexo-generator-searchdb --save
  2. +
  3. In Hexo site __config.yml, set:

    +
    1
    2
    3
    4
    5
    search:
    path: search.xml
    field: post
    content: true
    format: html
  4. +
  5. In Next Theme __config.yml, set:

    +
    1
    2
    local_search:
    enable: true
  6. +
+

When clicking search, the index file search.xml needs to +be downloaded and then searched.

+

Pros:

+
    +
  1. The index file search.xml coexists with the blog, which +is stable and reliable.
  2. +
  3. No search restrictions.
  4. +
+

Cons:

+
    +
  1. When the number of blogs is large, the search.xml file +will be large and slow to load.
  2. +
+

I use this search method on my Chinese site.

+

Reference

+ +

There are two plugins that support Algolia search: +hexo-algolia and hexo-algoliasearch, the +former can only search article titles, while the latter can search +article content. I use the latter for the English site.

+
    +
  1. Create an index in Algolia

  2. +
  3. Record Application ID, +Search-Only API Key, Admin Key

  4. +
  5. Install +npm install hexo-algoliasearch --save

  6. +
  7. In Hexo site __config.yml, set:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    algolia:
    appId: "Your App ID"
    apiKey: "Your Search Only API Key"
    adminApiKey: "40321c7c207e7f73b63a19aa24c4761b"
    chunkSize: 5000
    indexName: "Your Index Name"
    fields:
    - content:strip:truncate,0,500
    - excerpt:strip
    - gallery
    - permalink
    - photos
    - slug
    - tags
    - title
  8. +
  9. In Next Theme __config.yml, set:

    +
    1
    2
    3
    4
    algolia_search:
    enable: true
    hits:
    per_page: 10
  10. +
+

I think this way will be better: If it is found, it will display the +found results, instead of showing that XXX is found within XXX +milliseconds, like Google, Baidu does. If it is not found, it will +display "not found".

+

In +<BlogDir>/themes/next/source/js/third-party/search/algolia-search.js, +delete:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
instantsearch.widgets.stats({
container: '.algolia-stats',
templates: {
text: data => {
const stats = CONFIG.i18n.hits_time
.replace('${hits}', data.nbHits)
.replace('${time}', data.processingTimeMS);
return `<span>${stats}</span>
<img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia">`;
}
},
cssClasses: {
text: 'search-stats'
}
}),
+

Pros:

+
    +
  1. No need to preload, fast speed
  2. +
+

Cons:

+
    +
  1. The free version has a limit of 10,000 searches per month.
  2. +
  3. Every time you update an article, you need to create a new +index.
  4. +
  5. In China, search may be slow due to slow internet.
  6. +
+

Reference:Next +reference dochexo-algolia +reference docHexo +Algoliasearch

+

Add comment system

+

The comment system that comes with the Next theme is valine, which +is a comment system without a backend, and it is said that there are +some information security issues. So I use waline. First we need to follow +waline's configuration document to set the required components, and also +install the plugin Hexo NexT +Waline:

+
1
npm install @waline/hexo-next --save
+

In Hexo site __config.yml, set:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# Waline Config File
# For more information:
# - https://waline.js.org
# - https://waline.js.org/reference/component.html
waline:
# New! Whether enable this plugin
enable: true

# Waline server address url, you should set this to your own link
serverURL: 'https://waline-server-five-pi.vercel.app/'

# Waline library CDN url, you can set this to your preferred CDN
libUrl: https://unpkg.com/@waline/client@v2/dist/waline.js


# Waline CSS styles CDN url, you can set this to your preferred CDN
cssUrl: https://unpkg.com/@waline/client@v2/dist/waline.css

# Custom locales
locale:
placeholder: Welcome to comment # Comment box placeholder

# If false, comment count will only be displayed in post page, not in home page
commentCount: false

# Pageviews count, Note: You should not enable both `waline.pageview` and `leancloud_visitors`.
pageview: true

# Custom emoji
emoji:
# - https://unpkg.com/@waline/emojis@1.0.1/weibo
# - https://unpkg.com/@waline/emojis@1.0.1/alus
# - https://unpkg.com/@waline/emojis@1.0.1/bilibili
# - https://unpkg.com/@waline/emojis@1.0.1/qq
# - https://unpkg.com/@waline/emojis@1.0.1/tieba
# - https://unpkg.com/@waline/emojis@1.0.1/tw-emoji

# Comment infomation, valid meta are nick, mail and link
meta:
- nick
- mail
# - link

# Set required meta field, e.g.: [nick] | [nick, mail]
# requiredMeta:
# - nick

# Language, available values: en-US, zh-CN, zh-TW, pt-BR, ru-RU, jp-JP
lang: en-US

# Word limit, no limit when setting to 0
wordLimit: 0

# Whether enable login, can choose from 'enable', 'disable' and 'force'
login: enable

# comment per page
pageSize: 10
+

If you don't want to enable comments on a page, please set the +follows in the header of the article:

+
1
comments: false
+

The default comment description is Waline, which is very +strange. In <BlodDir>\themes\next\languages\en.yml, +add it under post:

+
1
2
3
post:
comments:
waline: Comments
+

then you modify Waline to Comments.

+

Set up FTP synchronization

+

Currently I use a virtual host to host my blog and need to use FTP to +upload files. According to ChaosTong/hexo-deployer-ftpsync, +I simply modified it to accommodate the latest node.js.

+

Install:

+
1
npm install git@github.com:littlesevenmo/hexo-deployer-ftpsync.git --save
+

In Hexo site __config.yml, set:

+
1
2
3
4
5
6
7
8
9
10
deploy:
type: ftpsync
host: Your FTP Host
user: Your FTP Username
pass: Your FTP Password
remote: /
port: 21
ignore:
connections: 1
verbose: false
+

Then you can use hexo d to deploy your blog.

+

Force access to blog with +SSH

+

Use FTP to upload a file named .htaccess to the root +directory of the site, the content is

+
1
2
3
RewriteEngine On
RewriteCond %{HTTP:KERSSL} !on
RewriteRule .* https://www.YourDomin.com/$1 [R=301,L]
+

Since the contents of the public folder will change +after hexo d, it has to be copied, pasted and uploaded +again each time. I use the alias command (function in Windows +PowerShell) to execute multiple commands at a time, as follows:

+
1
function blogd {cd D:\Blog ; hexo clean ; hexo g ; hexo algolia; Copy-Item -Path .\.htaccess -Destination .\public\; hexo d}
+

Please note that the setting method may be different under different +virtual hosts

+

Conclusion

+

The results of the above tests in my local are 0 Error, 0 Warning

+

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/96495af0/image-20230526213027939-1685786902862-1.png b/post/96495af0/image-20230526213027939-1685786902862-1.png new file mode 100644 index 0000000..f0a2ab5 Binary files /dev/null and b/post/96495af0/image-20230526213027939-1685786902862-1.png differ diff --git a/post/96495af0/image-20230526213027939.png b/post/96495af0/image-20230526213027939.png new file mode 100644 index 0000000..f0a2ab5 Binary files /dev/null and b/post/96495af0/image-20230526213027939.png differ diff --git a/post/96495af0/image-20230526222727457-1685786902862-3.png b/post/96495af0/image-20230526222727457-1685786902862-3.png new file mode 100644 index 0000000..81b0346 Binary files /dev/null and b/post/96495af0/image-20230526222727457-1685786902862-3.png differ diff --git a/post/96495af0/image-20230526222727457.png b/post/96495af0/image-20230526222727457.png new file mode 100644 index 0000000..81b0346 Binary files /dev/null and b/post/96495af0/image-20230526222727457.png differ diff --git a/post/96495af0/image-20230526222802693-1685786902862-2.png b/post/96495af0/image-20230526222802693-1685786902862-2.png new file mode 100644 index 0000000..0a1731c Binary files /dev/null and b/post/96495af0/image-20230526222802693-1685786902862-2.png differ diff --git a/post/96495af0/image-20230526222802693.png b/post/96495af0/image-20230526222802693.png new file mode 100644 index 0000000..0a1731c Binary files /dev/null and b/post/96495af0/image-20230526222802693.png differ diff --git a/post/96495af0/index.html b/post/96495af0/index.html new file mode 100644 index 0000000..49f09d7 --- /dev/null +++ b/post/96495af0/index.html @@ -0,0 +1,455 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Install HPCC Platforms and Setup ECL IDE | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Install HPCC Platforms and Setup ECL IDE +

+ + +
+ + + + +
+

HPCC Systems is a platform +purpose-built for high-speed data engineering. This article shows how to +build HPCC Platform and set up ECL IDE.

+ +

Install HPCC Platforms

+
    +
  1. Download the installation package of HPCC Platforms corresponding +to the operating system here. +For example, if my system is Ubuntu-22.04 LTS, then download this +file:

    +
    +image-20230526213027939 + +
  2. +
  3. Run +dpkg -i hpccsystems-platform-community_9.0.8-1jammy_amd64_withsymbols.deb. +There may be some errors at this time. Ignore it, please.

  4. +
  5. Run sudo apt-get update

  6. +
  7. Run sudo apt-get install -f

  8. +
  9. Verify that the program installed successfully: Run +cd /opt/HPCCSystems. The installation is successful if no +errors have occurred and the directory is entered successfully.

  10. +
  11. Check plugins: Run cd /opt/HPCCSystems/plugins, +ls -lart

  12. +
  13. Check current service status: Run +sudo /etc/init.d/hpcc-init status

  14. +
  15. Start HPCC service: Run +sudo /etc/init.d/hpcc-init start

  16. +
  17. Check the IP address: Run ip addr show. You can +access IP addr: 8010 to login ECL Watch

  18. +
+

Install ECL IDE

+

ECL IDE is an IDE specially designed for ECL programming language, +related documents can be found here.

+

First, download ECL IDE according to the platform, and find ECL IDE +and Client Tools here.

+

When you log in for the first time, the preference interface will pop +up, enter the IP obtained in Install HPCC Platforms in the IP +address, click Advanced, and leave Attribute Server blank. Other options +can be set according to your preferences.

+

In the ECL Folder of the Compiler tab, add your own code +directory.

+

HelloWorld

+

In the lower right Repository window, in My Files, right click and +Insert Folder, then in the newly created folder, right click and Insert +File.

+
+image-20230526222727457 + +
+

In the newly created file, enter:

+
1
OUTPUT('Hello World!');
+

Click Submit in the upper right corner, and you can see the result in +the result tab.

+
+image-20230526222802693 + +
+

Install the HPCC Systems +GNN bundle

+

The HPCC Systems machine learning library can be found here. +Among them, ML_Core is the +dependency of all other HPCC Systems machine learning packages.

+

Before installing the GNN bundle, you need to install TensorFlow +first.

+

My HPCC Platform is on WSL2-Ubuntu22.04, and my ECL IDE is on +Windows, so I need to install GNN Bundle in Windows.

+

run the below code in cmd:

+
1
2
"C:\Program Files (x86)\HPCCSystems\9.0.8\clienttools\bin\ecl.exe" bundle install https://github.com/hpcc-systems/ML_Core.git
"C:\Program Files (x86)\HPCCSystems\9.0.8\clienttools\bin\ecl.exe" bundle install https://github.com/hpcc-systems/GNN.git
+

You can test whether the installation is successful by running +GNN/Test/TensorAlignTest.

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/9be428e8/image-20230811223249032.png b/post/9be428e8/image-20230811223249032.png new file mode 100644 index 0000000..3b35acf Binary files /dev/null and b/post/9be428e8/image-20230811223249032.png differ diff --git a/post/9be428e8/image-20230811223338165.png b/post/9be428e8/image-20230811223338165.png new file mode 100644 index 0000000..53bd3f9 Binary files /dev/null and b/post/9be428e8/image-20230811223338165.png differ diff --git a/post/9be428e8/index.html b/post/9be428e8/index.html new file mode 100644 index 0000000..256da86 --- /dev/null +++ b/post/9be428e8/index.html @@ -0,0 +1,587 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2023 HPCC Systems Internship Summary | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 2023 HPCC Systems Internship Summary +

+ + +
+ + + + +
+

During my internship at HPCC Systems in 2023, I had the incredible +opportunity to work on a dynamic project that pushed the boundaries of +my technical skills and knowledge. In this post, I will summarize the +key aspects and achievements of my internship project, highlighting the +challenges I faced, the solutions I implemented, and the valuable +lessons I learned along the way.

+ +

Background and Motivation

+
    +
  • HPCC Systems: The HPCC +Systems platform is a remarkably powerful system for solving big data +challenges.

  • +
  • Generalized Neural +Network (GNN) bundle: This bundle provides a generalized ECL +interface to Keras over TensorFlow.

  • +
  • The GNN bundle was originally developed against the TensorFlow1.5 +interface. We currently support TensorFlow2.0 using a TensorFlow +compatibility mode. We want to achieve the following goals in this +project:

    +
      +
    • Adapt GNN to directly use the TensorFlow 2.0 +interface: Our primary objective is to migrate the GNN bundle +to directly integrate with the TensorFlow 2.0 interface, leveraging its +improved features and capabilities for enhanced performance and +efficiency.
    • +
    • Provide effective support for GPUs: We intend to +ensure effective support for GPUs, allowing users to harness the power +of graphical processing units for accelerated neural network +computations and faster training times.
    • +
    • Full support for pre-trained models: The upgraded +GNN bundle will be designed to fully support and accommodate the latest +and most advanced neural network models available. This will enable +users to leverage state-of-the-art architectures and achieve better +results for their specific tasks.
    • +
    • Save/Load trained model for later reuse: 1.We want +users to be able to save/load their trained models for later use.
    • +
  • +
+

Technical details

+

Upgrade functions to +TensorFlow2.x

+

We upgraded some functions to native TensorFlow2.x without +changing the API:

+
    +
  • DefineModel: Define a Keras / Tensorflow model +using Keras sytax.
  • +
  • DefineFuncModel: Construct a Keras Functional +Model.
  • +
+

These functions play a crucial role in defining the neural network +architecture. In the upgraded GNN bundle, both functions have been +enhanced to utilize the TensorFlow 2.x API. DefineModel +follows a sequential approach, where the layers are stacked in a strict +order. On the other hand, DefineFuncModel offers a more +flexible definition, allowing users to specify not only the shape of +each layer but also the connections between different layers. These +upgraded functions now support loading state-of-the-art models, which +provide users with more advanced and powerful neural network +architectures for their tasks.

+
    +
  • Fit: Train the model using training data.
  • +
+

The Fit function is used for training the neural +network. It allows users to set parameters such as batch size and epoch +number, providing greater control over the training process. With the +upgrade to TensorFlow 2.x, the training process becomes more efficient +and streamlined.

+
    +
  • EvaluateMod: Evaluate the trained model using +some test metrics.

  • +
  • Predict: Predict the results using the trained +model.

  • +
+

After training the model using the Fit function, the +EvaluateMod function helps users assess the model's +performance by providing metrics on how well it has been trained. The +Predict function allows users to apply the trained neural +network for tasks like recognition and classification.

+
    +
  • GetWeights: Get the weights currently associated +with the model.

  • +
  • SetWeights: Set the weights of the model from +the results of GetWeights.

  • +
+

These functions are essential for saving and loading models. Users +can use GetWeights to obtain the model's weights, and +SetWeights to set the model's weights, enabling easy model +persistence. This way, trained models can be saved and loaded, +facilitating model continuation for further training or deployment to +customers.

+

Create new functions

+

To enhance user experience and provide greater convenience, we have +introduced several new functions in the GNN bundle.

+
    +
  • GetModel: Get the structure and weights of the +model. This function allows users to access comprehensive details of the +model, including its structure and weights. By using +GetModel, users can gain a thorough understanding of the +neural network architecture.
  • +
  • SetModel: Create a Keras model from previously +saved DATASET.
  • +
+

Additionally, we have incorporated some developer-oriented functions +tailored for debugging purposes:

+
    +
  • GetSummary: Get the summary of the Keras model. The +GetSummary function offers developers access to the +underlying TensorFlow information. This allows developers to obtain +essential insights into the inner workings of the GNN bundle, aiding +them in identifying and resolving any issues that may arise.
  • +
  • IsGPUAvailable: Tests whether the GPU is available. +The IsGPUAvailable function serves as a practical tool for +developers to determine whether a GPU (Graphics Processing Unit) is +available for use. This information is crucial when optimizing the GNN +bundle's performance and ensuring efficient hardware utilization.
  • +
+

By providing these user-friendly and developer-oriented functions, we +strive to make the GNN bundle more accessible, efficient, and robust, +catering to the needs of both users and developers alike.

+

Support Pre-trained Model

+

The recently upgraded GNN bundle introduces support for numerous +state-of-the-art models, which have been defined and trained to serve +specific tasks. With over 70 pre-trained +models available, users can readily utilize them for their projects. +Additionally, these pre-trained models can act as the foundation for +user-specific models, allowing customization by modifying the input and +output and expanding the neural network, a technique commonly known as +transfer learning.

+

One notable example among the supported pre-trained models is +ResNet50. ResNet50 is a deep convolutional neural network comprising 50 +layers. By loading the pre-trained version of this network, which was +trained on an extensive dataset of more than a million images from the +ImageNet database, users gain access to a powerful image classifier. The +pre-trained neural network is capable of categorizing images into 1000 +object categories, including items such as keyboards, mice, pencils, as +well as various animals. As a result of its extensive training, this +neural network has acquired a vast knowledge of feature representations +for a diverse range of images.

+
+ResNet50 structure + +
+

Transfer Learning

+

The enhanced GNN bundle offers the ability to load numerous +pre-trained models, opening up possibilities for various impressive +applications. For instance, consider ResNet-50, a model commonly used to +classify 1000 different objects. However, if you wish to employ it +specifically for classifying vegetables or woolen cloth, you can achieve +this by leveraging the pre-trained ResNet-50 as a starting point for +training. This involves modifying the input and output components of the +model while preserving the existing ResNet-50 weights, rather than +initializing the weights randomly. Such an approach allows you to tailor +the model to your specific classification tasks effectively.

+

+

Also, you can use the pre-trained model as a part of your neural +network architecture, you can add layers to it.

+

Performance Testing

+

TL; DR:

+
    +
  • When using the same GPU, the upgraded GNN outperforms the previous +version with TensorFlow 1.x, achieving a remarkable 1.25 times +performance improvement.
  • +
  • The upgraded GNN takes advantage of GPU acceleration, resulting in a +substantial performance boost. When compared to using only CPU, the +performance improvement is an impressive 3.71 times.
  • +
+

FAQ

+

Q: Does it support video processing?

+

A: Yes!

+

Q: Did it change the existing API.

+

A: No. This means that the previous program can still be used without +modification.

+

Conclusion

+

A high point during my internship was successfully contributing to a +complex coding task, receiving recognition from the team for my +innovative approach. This achievement bolstered my confidence and +affirmed my passion for software development.

+

While on this journey, I encountered a low point when I faced an +unforeseen roadblock that temporarily hindered project progress. For +instance, when I come across unfamiliar grammar challenges within the +realm of the ECL programming language, I often find myself facing +significant obstacles. I am truly grateful for the assistance provided +by Lili and Roger, as their guidance proved instrumental in successfully +navigating through these complexities. This setback taught me resilience +and the importance of seeking guidance when confronted with unfamiliar +challenges.

+

The main takeaways from this internship include a heightened +appreciation for collaborative teamwork. These lessons will undoubtedly +shape my future endeavors in the field of technology.

+

References

+

The following article records the problems and solutions I +encountered in this project.

+ + +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/dc14f366/index.html b/post/dc14f366/index.html new file mode 100644 index 0000000..1db7686 --- /dev/null +++ b/post/dc14f366/index.html @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Calculate the execution time of operations in ECL | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Calculate the execution time of operations in ECL +

+ + +
+ + + + +
+

ECL is not a procedural language, that is, the order in which +operations are performed is not necessarily the order given by the code, +which can cause trouble in calculating the execution time of operations. +This blog describes how to calculate the execution time of certain +operations for users to evaluate performance.

+ +

In ECL, you can use the following code to get the current system +time:

+
1
2
IMPORT STD;
STD.Date.CurrentTime(TRUE);
+

Use ORDERED or SEQUENCIAL to force certain +operations to be executed in order:

+
1
2
3
4
ORDERED([OUTPUT(STD.Date.CurrentTime(TRUE), NAMED('startTime')), 
operations.....,
OUTPUT(STD.Date.CurrentTime(TRUE), NAMED('endTime')),
Some operations]);
+

Reference:

+

Did +you really mean SEQUENTIAL?

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/df26575/index.html b/post/df26575/index.html new file mode 100644 index 0000000..feb6d85 --- /dev/null +++ b/post/df26575/index.html @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Install CUDA and TensorFlow in WSL2 | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Install CUDA and TensorFlow in WSL2 +

+ + +
+ + + + +
+

Install directly in the system without using Anaconda.

+ +

Install CUDA

+

First, find the corresponding CUDA version according to the +TensorFlow you want to install, and install the corresponding CUDA. For +example, I want to install TensorFlow version 2.12, which uses CUDA +11.8, so I download CUDA from here. +I use Linux-X86-64-WSL-Ubuntu-2.0, and I choose the installation package +of runfile(local). This is the most convenient and recommended!

+

In the prompt box that pops up, select Install:

+
+image-20230602232528589 + +
+

After installation, you need to manually add environment variables in +~/.bashrc, add:

+
1
2
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/cuda-11.8/lib64
export PATH=/usr/local/cuda-11.8/bin${PATH:+:${PATH}}
+

relaunch terminal or source ~/.bashrc.

+

At this point, enter nvcc --versions to check whether +the installation is successful.

+
+image-20230602232739577 + +
+

Install TensorFlow

+

The installation +instructions of the HPCC GNN Bundle mention that

+
+

Tensorflow should be installed using su so that all users can see it, +and must be installed using the same version of Python3 as is embedded +in the HPCC Systems platform.

+
+
    +
  1. Enter root mode, su root

  2. +
  3. Install cuDNN: +pip install nvidia-cudnn-cu11==8.6.0.163

  4. +
  5. Add environment variables in ~/.bashrc:

    +
    1
    2
    CUDNN_PATH=$(dirname $(python -c "import nvidia.cudnn;print(nvidia.cudnn.__file__)"))
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CUDNN_PATH/lib
  6. +
  7. Upgrade pip: pip install --upgrade pip

  8. +
  9. Install TensorFlow using pip: +pip install tensorflow==2.12.*

  10. +
  11. Check whether the installation is successful: exit root mode and +run +python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))"

  12. +
  13. Or you can use this to show the name of GPU(s):

    +
    1
    2
    3
    4
    import tensorflow as tf

    gpu_device_name = tf.test.gpu_device_name()
    print(gpu_device_name)
  14. +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/e104d58f/index.html b/post/e104d58f/index.html new file mode 100644 index 0000000..b07f6c4 --- /dev/null +++ b/post/e104d58f/index.html @@ -0,0 +1,392 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Adjust the number of nodes in HPCC Systems | Boqiang's Blog + + + + + + + + + + + +
+ +
+
+
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + +
+ + 0% +
+ + + + +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Adjust the number of nodes in HPCC Systems +

+ + +
+ + + + +
+

Adjust the number of Nodes in HPCC Systems under WSL2.

+ +

I am using WSL2 - Ubuntu 22.04, and WSL2 uses all the CPUs in the +current Windows by default, so in this case, to modify the number of +nodes in HPCC Systems, you only need to set +slavesPerNode="The number of nodes you want to set" in +/etc/HPCCSystems/environment.xml.

+

Reference:

+
    +
  1. Advanced +settings configuration in WSL
  2. +
  3. Running HPCC +Systems Platform on Microsoft Hyper-V
  4. +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +