aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-02-08 05:47:43 +0000
committerGitHub <[email protected]>2026-02-08 05:47:43 +0000
commite3287c2bb8bbcace5ecabc7ce2bf2ed06897e906 (patch)
tree52702fc3ba105684a2f9b2d1dc1872985b2a5f59 /static
parent9473230c481c627fe6d14c8330a95cda1c659f58 (diff)
downloadthatcomputerscientist-e3287c2bb8bbcace5ecabc7ce2bf2ed06897e906.tar.xz
thatcomputerscientist-e3287c2bb8bbcace5ecabc7ce2bf2ed06897e906.zip
Added Fancy version of Miku with styles and new entry page for journals
Diffstat (limited to 'static')
-rw-r--r--static/css/miku/miku.css364
-rw-r--r--static/css/miku/themes/ghost.css102
-rw-r--r--static/css/miku/themes/lain.css107
-rw-r--r--static/css/miku/themes/midnight.css138
-rw-r--r--static/css/miku/themes/neoncity.css102
-rw-r--r--static/css/miku/themes/shifoo.css106
-rw-r--r--static/css/miku/themes/vintage.css106
-rw-r--r--static/js/miku/fancymiku.js761
8 files changed, 1786 insertions, 0 deletions
diff --git a/static/css/miku/miku.css b/static/css/miku/miku.css
index 2eb57fbe..15f52718 100644
--- a/static/css/miku/miku.css
+++ b/static/css/miku/miku.css
@@ -228,4 +228,368 @@
.miku-syntax-js-function {
color: #9b59b6;
font-weight: 600;
+}
+
+.miku-container {
+ position: relative;
+ width: 100%;
+ border-radius: 6px;
+ overflow: hidden;
+ font-family: 'Mali', sans-serif;
+ background: #0a0a0a;
+ border: 2px solid #333;
+}
+
+.miku-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ padding: 10px;
+ background: #1a1a1a;
+ border-bottom: 1px solid #333;
+}
+
+.miku-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 32px;
+ height: 32px;
+ padding: 6px 10px;
+ background: #2a2a2a;
+ border: 1px solid #444;
+ border-radius: 4px;
+ color: #e8e8e8;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ font-family: 'Mali', sans-serif;
+}
+
+.miku-btn:hover {
+ background: #3a3a3a;
+ border-color: #666;
+}
+
+.miku-btn-special {
+ background: #333;
+ border-color: #555;
+}
+
+.miku-toolbar-separator {
+ width: 1px;
+ height: 24px;
+ background: #444;
+ margin: 4px 2px;
+}
+
+.miku-content,
+.miku-source {
+ padding: 16px;
+ background: #0f0f0f;
+ color: #e8e8e8;
+ font-size: 13px;
+ line-height: 1.6;
+ overflow-y: auto;
+ overflow-x: hidden;
+ min-height: 300px;
+ outline: none;
+}
+
+.miku-source {
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ background: #0a0a0a;
+ border: none;
+ resize: none;
+ width: 100%;
+ padding: 16px;
+ color: #e8e8e8;
+}
+
+.miku-content::-webkit-scrollbar,
+.miku-source::-webkit-scrollbar {
+ width: 8px;
+}
+
+.miku-content::-webkit-scrollbar-track,
+.miku-source::-webkit-scrollbar-track {
+ background: #1a1a1a;
+}
+
+.miku-content::-webkit-scrollbar-thumb,
+.miku-source::-webkit-scrollbar-thumb {
+ background: #4ecdc4;
+ border-radius: 4px;
+}
+
+.miku-content:empty:before,
+.miku-content:has(p:only-child:empty):before {
+ content: attr(data-placeholder);
+ color: #666;
+ pointer-events: none;
+ position: absolute;
+}
+
+.miku-content p {
+ margin: 4px 0;
+ text-align: justify;
+}
+
+.miku-content h1,
+.miku-content h2,
+.miku-content h3 {
+ margin: 16px 0 8px 0;
+ color: #4ecdc4;
+ font-weight: 600;
+}
+
+.miku-content h1 {
+ font-size: 24px;
+}
+
+.miku-content h2 {
+ font-size: 20px;
+}
+
+.miku-content h3 {
+ font-size: 16px;
+}
+
+.miku-content strong,
+.miku-content b {
+ font-weight: 700;
+ color: #ff6b6b;
+}
+
+.miku-content em,
+.miku-content i {
+ font-style: italic;
+ color: #f39c12;
+}
+
+.miku-content u {
+ text-decoration: underline;
+}
+
+.miku-content s {
+ text-decoration: line-through;
+ opacity: 0.7;
+}
+
+.miku-content a {
+ color: #45b7d1;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.miku-link-edit {
+ display: inline-block;
+ margin-left: 4px;
+ padding: 2px 6px;
+ background: #333;
+ color: #4ecdc4;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 12px;
+}
+
+.miku-content img {
+ max-width: 350px;
+ max-height: 350px;
+ border-radius: 8px;
+ border: 1px solid #333;
+ margin: 6px 12px 8px 0;
+ cursor: pointer;
+}
+
+.miku-content img:nth-of-type(odd) {
+ float: left;
+ margin-right: 12px;
+}
+
+.miku-content img:nth-of-type(even) {
+ float: right;
+ margin-left: 12px;
+}
+
+.miku-content img.block {
+ float: none;
+ display: block;
+ margin: 16px auto;
+ max-width: 100%;
+ max-height: 500px;
+ width: auto;
+}
+
+.miku-content a img {
+ transition: transform 0.2s ease, opacity 0.2s ease;
+}
+
+.miku-content a img:hover {
+ transform: scale(1.02);
+ opacity: 0.9;
+}
+
+.miku-content blockquote {
+ margin: 12px 0;
+ padding: 12px 16px;
+ border-left: 4px solid #4ecdc4;
+ background: rgba(78, 205, 196, 0.1);
+ border-radius: 0 4px 4px 0;
+ font-style: italic;
+}
+
+.miku-content code {
+ background: #1a1a1a;
+ color: #4ecdc4;
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 12px;
+ border: 1px solid #333;
+}
+
+.miku-code-wrapper {
+ position: relative;
+ margin: 16px 0;
+ border: 1px solid #333;
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+.miku-code-lang {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ background: #1a1a1a;
+ color: #4ecdc4;
+ padding: 4px 8px;
+ border: 1px solid #333;
+ border-radius: 3px;
+ font-size: 11px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ z-index: 10;
+ cursor: pointer;
+}
+
+.miku-content pre {
+ margin: 0;
+ padding: 40px 12px 12px 12px;
+ background: #0a0a0a;
+ overflow-x: auto;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 12px;
+ line-height: 1.5;
+ color: #e8e8e8;
+}
+
+.miku-content pre code {
+ background: transparent;
+ border: none;
+ padding: 0;
+ color: inherit;
+}
+
+.miku-popup {
+ position: fixed;
+ width: 400px;
+ background: #1a1a1a;
+ border: 2px solid #333;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
+ z-index: 10000;
+ font-family: 'Mali', sans-serif;
+}
+
+.miku-popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ background: #2a2a2a;
+ border-bottom: 1px solid #333;
+ color: #e8e8e8;
+ font-weight: 600;
+}
+
+.miku-popup-close {
+ background: none;
+ border: none;
+ color: #e8e8e8;
+ font-size: 24px;
+ cursor: pointer;
+ padding: 0;
+ width: 24px;
+ height: 24px;
+ line-height: 1;
+}
+
+.miku-popup-body {
+ padding: 16px;
+}
+
+.miku-popup-field {
+ margin-bottom: 12px;
+}
+
+.miku-popup-field label {
+ display: block;
+ color: #4ecdc4;
+ margin-bottom: 6px;
+ font-size: 13px;
+}
+
+.miku-popup-field input[type="text"],
+.miku-popup-field select {
+ width: 100%;
+ background: #0f0f0f;
+ border: 1px solid #333;
+ border-radius: 4px;
+ padding: 8px;
+ color: #e8e8e8;
+ font-family: 'Mali', sans-serif;
+ font-size: 13px;
+}
+
+.miku-popup-field input[type="checkbox"] {
+ margin-right: 6px;
+}
+
+.miku-popup-footer {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+ padding: 12px 16px;
+ background: #2a2a2a;
+ border-top: 1px solid #333;
+}
+
+.miku-btn-primary,
+.miku-btn-secondary {
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-weight: 600;
+ cursor: pointer;
+ font-family: 'Mali', sans-serif;
+ font-size: 13px;
+}
+
+.miku-btn-primary {
+ background: #4ecdc4;
+ border: 1px solid #4ecdc4;
+ color: #000;
+}
+
+.miku-btn-primary:hover {
+ background: #5fd3ca;
+}
+
+.miku-btn-secondary {
+ background: #2a2a2a;
+ border: 1px solid #444;
+ color: #e8e8e8;
+}
+
+.miku-btn-secondary:hover {
+ background: #3a3a3a;
} \ No newline at end of file
diff --git a/static/css/miku/themes/ghost.css b/static/css/miku/themes/ghost.css
index a57ca488..6a02819b 100644
--- a/static/css/miku/themes/ghost.css
+++ b/static/css/miku/themes/ghost.css
@@ -145,4 +145,106 @@
.miku-syntax-js-function {
color: #00FF00;
+}
+
+.miku-container {
+ background: #000000;
+ border: 1px solid #00FFFF;
+}
+
+.miku-toolbar {
+ background: #000000;
+ border-bottom: 1px solid #00FFFF;
+}
+
+.miku-btn {
+ color: #00FFFF;
+ background: transparent;
+ border: 1px solid #004444;
+}
+
+.miku-btn:hover {
+ background: #001111;
+ border-color: #00FFFF;
+}
+
+.miku-content,
+.miku-source {
+ background: #000000;
+ color: #00FFFF;
+}
+
+.miku-content::-webkit-scrollbar-thumb,
+.miku-source::-webkit-scrollbar-thumb {
+ background: #00FFFF;
+}
+
+.miku-content h1,
+.miku-content h2,
+.miku-content h3,
+.miku-content a {
+ color: #00FFFF;
+}
+
+.miku-link-edit {
+ background: #001111;
+ color: #00FFFF;
+}
+
+.miku-content img {
+ border-color: #004444;
+}
+
+.miku-content blockquote {
+ border-left-color: #00FFFF;
+ background: #001111;
+}
+
+.miku-content code {
+ background: #001111;
+ color: #00FFFF;
+ border-color: #004444;
+}
+
+.miku-code-wrapper,
+.miku-content pre {
+ border-color: #004444;
+ background: #001111;
+}
+
+.miku-code-lang {
+ background: #000000;
+ color: #00FFFF;
+ border-color: #00FFFF;
+}
+
+.miku-popup {
+ background: #000000;
+ border: 1px solid #00FFFF;
+}
+
+.miku-popup-header,
+.miku-popup-footer {
+ background: #000000;
+ border-color: #00FFFF;
+ color: #00FFFF;
+}
+
+.miku-popup-field label,
+.miku-popup-field input[type="text"],
+.miku-popup-field select {
+ color: #00FFFF;
+ background: #001111;
+ border-color: #00FFFF;
+}
+
+.miku-btn-primary {
+ background: #00FFFF;
+ color: #000000;
+}
+
+.miku-btn-secondary {
+ background: transparent;
+ color: #00FFFF;
+ border: 1px solid #00FFFF;
} \ No newline at end of file
diff --git a/static/css/miku/themes/lain.css b/static/css/miku/themes/lain.css
index 3ca95242..eaf816d7 100644
--- a/static/css/miku/themes/lain.css
+++ b/static/css/miku/themes/lain.css
@@ -145,4 +145,111 @@
.miku-syntax-js-function {
color: #AD717B;
+}
+
+.miku-container {
+ background: #000000;
+ border: 1px solid #C4748D;
+}
+
+.miku-toolbar {
+ background: #000000;
+ border-bottom: 1px solid #C4748D;
+}
+
+.miku-btn {
+ color: #AD717B;
+ background: transparent;
+ border: 1px solid #85474B;
+}
+
+.miku-btn:hover {
+ background: #340000;
+ border-color: #C4748D;
+ color: #D4758C;
+}
+
+.miku-content,
+.miku-source {
+ background: #000000;
+ color: #AD717B;
+}
+
+.miku-content::-webkit-scrollbar-thumb,
+.miku-source::-webkit-scrollbar-thumb {
+ background: #C4748D;
+}
+
+.miku-content h1,
+.miku-content h2,
+.miku-content h3,
+.miku-content a {
+ color: #D4758C;
+}
+
+.miku-link-edit {
+ background: #340000;
+ color: #D4758C;
+}
+
+.miku-content img {
+ border-color: #85474B;
+}
+
+.miku-content blockquote {
+ border-left-color: #C4748D;
+ background: #340000;
+ color: #AD717B;
+}
+
+.miku-content code {
+ background: #340000;
+ color: #D4758C;
+ border-color: #85474B;
+}
+
+.miku-code-wrapper,
+.miku-content pre {
+ border-color: #85474B;
+ background: #340000;
+}
+
+.miku-code-lang {
+ background: #000000;
+ color: #AD717B;
+ border-color: #C4748D;
+}
+
+.miku-popup {
+ background: #000000;
+ border: 1px solid #C4748D;
+}
+
+.miku-popup-header,
+.miku-popup-footer {
+ background: #000000;
+ border-color: #C4748D;
+ color: #D4758C;
+}
+
+.miku-popup-field label {
+ color: #AD717B;
+}
+
+.miku-popup-field input[type="text"],
+.miku-popup-field select {
+ background: #340000;
+ border-color: #C4748D;
+ color: #AD717B;
+}
+
+.miku-btn-primary {
+ background: #C4748D;
+ color: #000000;
+}
+
+.miku-btn-secondary {
+ background: transparent;
+ color: #AD717B;
+ border: 1px solid #C4748D;
} \ No newline at end of file
diff --git a/static/css/miku/themes/midnight.css b/static/css/miku/themes/midnight.css
index 352669b6..c63bdffc 100644
--- a/static/css/miku/themes/midnight.css
+++ b/static/css/miku/themes/midnight.css
@@ -136,4 +136,142 @@
.miku-syntax-js-function {
color: #8d8dff;
+}
+
+.miku-container {
+ background: #000;
+ border-color: #8d8dff;
+}
+
+.miku-toolbar {
+ background: rgba(15, 10, 25, 0.9);
+ border-bottom-color: rgba(173, 140, 255, 0.4);
+}
+
+.miku-btn {
+ background: rgba(68, 68, 177, 0.3);
+ border-color: rgba(141, 141, 255, 0.4);
+ color: #7ee8c7;
+}
+
+.miku-btn:hover {
+ background: rgba(223, 35, 196, 0.5);
+ border-color: #df23c4;
+ color: #ffffff;
+}
+
+.miku-content,
+.miku-source {
+ background: #000;
+ color: #fff;
+}
+
+.miku-content::-webkit-scrollbar-thumb,
+.miku-source::-webkit-scrollbar-thumb {
+ background: #df23c4;
+}
+
+.miku-content h1,
+.miku-content h2,
+.miku-content h3 {
+ color: #7ee8c7;
+ text-shadow: 0 0 4px rgba(126, 232, 199, 0.3);
+}
+
+.miku-content strong,
+.miku-content b {
+ color: #96b5ff;
+}
+
+.miku-content em,
+.miku-content i {
+ color: #ff96c4;
+}
+
+.miku-content a {
+ color: #df23c4;
+ text-shadow: 0 0 4px rgba(223, 35, 196, 0.3);
+}
+
+.miku-link-edit {
+ background: rgba(68, 68, 177, 0.8);
+ color: #7ee8c7;
+}
+
+.miku-content img {
+ border-color: rgba(141, 141, 255, 0.3);
+ box-shadow: 0 0 8px rgba(141, 141, 255, 0.2);
+}
+
+.miku-content blockquote {
+ border-left-color: #8d8dff;
+ background: rgba(141, 141, 255, 0.1);
+ color: #96b5ff;
+}
+
+.miku-content code {
+ background: rgba(0, 0, 0, 0.4);
+ color: #54f2f2;
+ border-color: rgba(84, 242, 242, 0.3);
+}
+
+.miku-code-wrapper,
+.miku-content pre {
+ border-color: rgba(141, 141, 255, 0.3);
+ background: rgba(0, 0, 0, 0.5);
+}
+
+.miku-code-lang {
+ background: rgba(141, 141, 255, 0.3);
+ color: #7ee8c7;
+ border-color: rgba(141, 141, 255, 0.5);
+}
+
+.miku-popup {
+ background: rgba(30, 20, 50, 0.95);
+ border-color: rgba(173, 140, 255, 0.7);
+ box-shadow: 0 8px 24px rgba(136, 87, 210, 0.6);
+}
+
+.miku-popup-header {
+ background: rgba(50, 30, 80, 0.95);
+ border-bottom-color: rgba(173, 140, 255, 0.5);
+}
+
+.miku-popup-field label {
+ color: #7ee8c7;
+}
+
+.miku-popup-field input[type="text"],
+.miku-popup-field select {
+ background: rgba(0, 0, 0, 0.6);
+ border-color: rgba(141, 141, 255, 0.4);
+ color: #fff;
+}
+
+.miku-popup-footer {
+ background: rgba(50, 30, 80, 0.95);
+ border-top-color: rgba(173, 140, 255, 0.5);
+}
+
+.miku-btn-primary {
+ background: rgba(68, 68, 177, 0.85);
+ border-color: #8d8dff;
+ color: #fff;
+}
+
+.miku-btn-primary:hover {
+ background: #df23c4;
+ border-color: #df23c4;
+}
+
+.miku-btn-secondary {
+ background: rgba(68, 68, 177, 0.3);
+ border-color: rgba(141, 141, 255, 0.4);
+ color: #96b5ff;
+}
+
+.miku-btn-secondary:hover {
+ background: rgba(150, 181, 255, 0.2);
+ color: #ffffff;
} \ No newline at end of file
diff --git a/static/css/miku/themes/neoncity.css b/static/css/miku/themes/neoncity.css
index 963207f3..7ed96c89 100644
--- a/static/css/miku/themes/neoncity.css
+++ b/static/css/miku/themes/neoncity.css
@@ -146,4 +146,106 @@
.miku-syntax-js-function {
color: #00ff00;
font-weight: bold;
+}
+
+.miku-container {
+ background: #000000;
+ border: none;
+}
+
+.miku-toolbar {
+ background: #000000;
+ border-bottom: 1px solid #00ff00;
+}
+
+.miku-btn {
+ color: #00ff00;
+ background: transparent;
+ border: 1px solid #003300;
+}
+
+.miku-btn:hover {
+ background: #111111;
+ border-color: #00ff00;
+}
+
+.miku-content,
+.miku-source {
+ background: #000000;
+ color: #00ff00;
+}
+
+.miku-content::-webkit-scrollbar-thumb,
+.miku-source::-webkit-scrollbar-thumb {
+ background: #00ff00;
+}
+
+.miku-content h1,
+.miku-content h2,
+.miku-content h3,
+.miku-content a {
+ color: #00ff00;
+}
+
+.miku-link-edit {
+ background: #111111;
+ color: #00ff00;
+}
+
+.miku-content img {
+ border-color: #003300;
+}
+
+.miku-content blockquote {
+ border-left-color: #00ff00;
+ background: #111111;
+}
+
+.miku-content code {
+ background: #111111;
+ color: #00ff00;
+ border-color: #003300;
+}
+
+.miku-code-wrapper,
+.miku-content pre {
+ border-color: #003300;
+ background: #111111;
+}
+
+.miku-code-lang {
+ background: #000000;
+ color: #00ff00;
+ border-color: #00ff00;
+}
+
+.miku-popup {
+ background: #000000;
+ border: 1px solid #00ff00;
+}
+
+.miku-popup-header,
+.miku-popup-footer {
+ background: #000000;
+ border-color: #00ff00;
+ color: #00ff00;
+}
+
+.miku-popup-field label,
+.miku-popup-field input[type="text"],
+.miku-popup-field select {
+ color: #00ff00;
+ background: #111111;
+ border-color: #00ff00;
+}
+
+.miku-btn-primary {
+ background: #00ff00;
+ color: #000000;
+}
+
+.miku-btn-secondary {
+ background: transparent;
+ color: #00ff00;
+ border: 1px solid #00ff00;
} \ No newline at end of file
diff --git a/static/css/miku/themes/shifoo.css b/static/css/miku/themes/shifoo.css
index 4a3abe28..8da6c0eb 100644
--- a/static/css/miku/themes/shifoo.css
+++ b/static/css/miku/themes/shifoo.css
@@ -142,4 +142,110 @@
.miku-syntax-js-function {
color: #5ccfe6;
+}
+
+.miku-container {
+ background: #0a0e14;
+ border: none;
+}
+
+.miku-toolbar {
+ background: #0a0e14;
+ border-bottom: 1px solid #4d5566;
+}
+
+.miku-btn {
+ color: #b3b1ad;
+ background: transparent;
+ border: 1px solid #1a1f29;
+}
+
+.miku-btn:hover {
+ background: #1a1f29;
+ border-color: #36a3d9;
+ color: #ffb454;
+}
+
+.miku-content,
+.miku-source {
+ background: #0a0e14;
+ color: #b3b1ad;
+}
+
+.miku-content::-webkit-scrollbar-thumb,
+.miku-source::-webkit-scrollbar-thumb {
+ background: #36a3d9;
+}
+
+.miku-content h1,
+.miku-content h2,
+.miku-content h3,
+.miku-content a {
+ color: #36a3d9;
+}
+
+.miku-link-edit {
+ background: #1a1f29;
+ color: #ffb454;
+}
+
+.miku-content img {
+ border-color: #4d5566;
+}
+
+.miku-content blockquote {
+ border-left-color: #36a3d9;
+ background: #1a1f29;
+}
+
+.miku-content code {
+ background: #1a1f29;
+ color: #ffb454;
+ border-color: #4d5566;
+}
+
+.miku-code-wrapper,
+.miku-content pre {
+ border-color: #4d5566;
+ background: #1a1f29;
+}
+
+.miku-code-lang {
+ background: #0a0e14;
+ color: #b3b1ad;
+ border-color: #36a3d9;
+}
+
+.miku-popup {
+ background: #0a0e14;
+ border: 1px solid #4d5566;
+}
+
+.miku-popup-header,
+.miku-popup-footer {
+ background: #0a0e14;
+ border-color: #4d5566;
+ color: #ffb454;
+}
+
+.miku-popup-field label {
+ color: #b3b1ad;
+}
+
+.miku-popup-field input[type="text"],
+.miku-popup-field select {
+ background: #1a1f29;
+ border-color: #4d5566;
+ color: #b3b1ad;
+}
+
+.miku-btn-primary {
+ background: #36a3d9;
+ color: #0a0e14;
+}
+
+.miku-btn-secondary {
+ background: transparent;
+ color: #b3b1ad;
+ border: 1px solid #4d5566;
} \ No newline at end of file
diff --git a/static/css/miku/themes/vintage.css b/static/css/miku/themes/vintage.css
index 34afcee8..65b4ec19 100644
--- a/static/css/miku/themes/vintage.css
+++ b/static/css/miku/themes/vintage.css
@@ -139,4 +139,110 @@
.miku-syntax-js-function {
color: #8d8dff;
+}
+
+.miku-container {
+ background: rgba(0, 0, 0, 0.3);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.miku-toolbar {
+ background: rgba(0, 0, 0, 0.2);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.miku-btn {
+ color: #ffffff;
+ background: transparent;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.miku-btn:hover {
+ background: rgba(141, 141, 255, 0.2);
+ border-color: #8d8dff;
+ color: #8d8dff;
+}
+
+.miku-content,
+.miku-source {
+ background: transparent;
+ color: #ffffff;
+}
+
+.miku-content::-webkit-scrollbar-thumb,
+.miku-source::-webkit-scrollbar-thumb {
+ background: #8d8dff;
+}
+
+.miku-content h1,
+.miku-content h2,
+.miku-content h3,
+.miku-content a {
+ color: #8d8dff;
+}
+
+.miku-link-edit {
+ background: rgba(0, 0, 0, 0.4);
+ color: #8d8dff;
+}
+
+.miku-content img {
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+.miku-content blockquote {
+ border-left-color: #8d8dff;
+ background: rgba(0, 0, 0, 0.3);
+}
+
+.miku-content code {
+ background: rgba(0, 0, 0, 0.4);
+ color: #8d8dff;
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+.miku-code-wrapper,
+.miku-content pre {
+ border-color: rgba(255, 255, 255, 0.1);
+ background: rgba(0, 0, 0, 0.4);
+}
+
+.miku-code-lang {
+ background: rgba(0, 0, 0, 0.3);
+ color: #ffffff;
+ border-color: rgba(141, 141, 255, 0.3);
+}
+
+.miku-popup {
+ background: rgba(0, 0, 0, 0.95);
+ border: 1px solid rgba(141, 141, 255, 0.3);
+}
+
+.miku-popup-header,
+.miku-popup-footer {
+ background: rgba(0, 0, 0, 0.95);
+ border-color: rgba(255, 255, 255, 0.1);
+ color: #8d8dff;
+}
+
+.miku-popup-field label {
+ color: #ffffff;
+}
+
+.miku-popup-field input[type="text"],
+.miku-popup-field select {
+ background: rgba(0, 0, 0, 0.5);
+ border-color: rgba(141, 141, 255, 0.3);
+ color: #ffffff;
+}
+
+.miku-btn-primary {
+ background: #8d8dff;
+ color: #000000;
+}
+
+.miku-btn-secondary {
+ background: transparent;
+ color: #ffffff;
+ border: 1px solid rgba(255, 255, 255, 0.3);
} \ No newline at end of file
diff --git a/static/js/miku/fancymiku.js b/static/js/miku/fancymiku.js
new file mode 100644
index 00000000..adfd2197
--- /dev/null
+++ b/static/js/miku/fancymiku.js
@@ -0,0 +1,761 @@
+(function () {
+ 'use strict';
+
+ const LANGUAGES = [
+ 'python', 'javascript', 'typescript', 'java', 'c', 'cpp', 'csharp', 'php', 'ruby', 'go',
+ 'rust', 'swift', 'kotlin', 'scala', 'r', 'matlab', 'julia', 'perl', 'bash', 'shell',
+ 'powershell', 'sql', 'html', 'css', 'scss', 'less', 'xml', 'json', 'yaml', 'toml',
+ 'markdown', 'latex', 'dart', 'elixir', 'erlang', 'haskell', 'lua', 'objective-c',
+ 'fortran', 'assembly', 'vhdl', 'verilog', 'graphql', 'dockerfile', 'nginx', 'apache'
+ ];
+
+ class FancyMiku {
+ constructor(container, options = {}) {
+ this.container = typeof container === 'string' ? document.querySelector(container) : container;
+ this.options = {
+ height: options.height || '500px',
+ placeholder: options.placeholder || 'Start writing...',
+ onChange: options.onChange || null
+ };
+
+ this.savedSelection = null;
+ this.isSourceMode = false;
+ this.currentPopup = null;
+ this.init();
+ }
+
+ init() {
+ this.container.innerHTML = '';
+ this.createEditor();
+ this.attachEventListeners();
+ }
+
+ createEditor() {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'miku-container';
+
+ const toolbar = this.createToolbar();
+ wrapper.appendChild(toolbar);
+
+ const editableArea = document.createElement('div');
+ editableArea.className = 'miku-content';
+ editableArea.contentEditable = 'true';
+ editableArea.setAttribute('data-placeholder', this.options.placeholder);
+ editableArea.style.height = this.options.height;
+ editableArea.innerHTML = '<p><br></p>';
+ wrapper.appendChild(editableArea);
+
+ const sourceArea = document.createElement('textarea');
+ sourceArea.className = 'miku-source';
+ sourceArea.style.height = this.options.height;
+ sourceArea.style.display = 'none';
+ wrapper.appendChild(sourceArea);
+
+ this.container.appendChild(wrapper);
+ this.wrapper = wrapper;
+ this.toolbar = toolbar;
+ this.editableArea = editableArea;
+ this.sourceArea = sourceArea;
+ }
+
+ createToolbar() {
+ const toolbar = document.createElement('div');
+ toolbar.className = 'miku-toolbar';
+
+ const buttons = [
+ { icon: 'H1', title: 'Heading 1', command: 'heading', value: 'h1' },
+ { icon: 'H2', title: 'Heading 2', command: 'heading', value: 'h2' },
+ { icon: 'H3', title: 'Heading 3', command: 'heading', value: 'h3' },
+ { separator: true },
+ { icon: '<strong>B</strong>', title: 'Bold', command: 'bold' },
+ { icon: '<em>I</em>', title: 'Italic', command: 'italic' },
+ { icon: '<u>U</u>', title: 'Underline', command: 'underline' },
+ { icon: '<s>S</s>', title: 'Strikethrough', command: 'strikethrough' },
+ { separator: true },
+ { icon: '🔗', title: 'Insert Link', command: 'link' },
+ { icon: '🖼️', title: 'Insert Image', command: 'image' },
+ { separator: true },
+ { icon: '❝❞', title: 'Blockquote', command: 'blockquote' },
+ { icon: '</>', title: 'Code Block', command: 'codeblock' },
+ { icon: '`', title: 'Inline Code', command: 'inlinecode' },
+ { separator: true },
+ { icon: '⇄', title: 'Toggle Source', command: 'togglesource', special: true }
+ ];
+
+ buttons.forEach(btn => {
+ if (btn.separator) {
+ const separator = document.createElement('span');
+ separator.className = 'miku-toolbar-separator';
+ toolbar.appendChild(separator);
+ } else {
+ const button = document.createElement('button');
+ button.type = 'button';
+ button.className = 'miku-btn';
+ if (btn.special) button.classList.add('miku-btn-special');
+ button.innerHTML = btn.icon;
+ button.title = btn.title;
+ button.dataset.command = btn.command;
+ if (btn.value) button.dataset.value = btn.value;
+ toolbar.appendChild(button);
+ }
+ });
+
+ return toolbar;
+ }
+
+ attachEventListeners() {
+ this.toolbar.addEventListener('click', (e) => {
+ const btn = e.target.closest('.miku-btn');
+ if (!btn) return;
+ e.preventDefault();
+ this.executeCommand(btn.dataset.command, btn.dataset.value);
+ });
+
+ this.editableArea.addEventListener('mouseup', () => this.saveSelection());
+ this.editableArea.addEventListener('keyup', () => {
+ this.saveSelection();
+ this.handleLinkEditing();
+ });
+ this.editableArea.addEventListener('click', (e) => this.handleLinkClick(e));
+ this.editableArea.addEventListener('keydown', (e) => this.handleKeyDown(e));
+
+ this.editableArea.addEventListener('input', () => {
+ this.ensureParagraphStructure();
+ if (this.options.onChange) {
+ this.options.onChange(this.getContent());
+ }
+ });
+
+ this.editableArea.addEventListener('paste', (e) => this.handlePaste(e));
+
+ this.editableArea.addEventListener('blur', () => {
+ if (this.editableArea.innerHTML.trim() === '') {
+ this.editableArea.innerHTML = '<p><br></p>';
+ }
+ });
+
+ document.addEventListener('click', (e) => {
+ if (this.currentPopup && !this.currentPopup.contains(e.target) && !e.target.closest('.miku-btn')) {
+ this.closePopup();
+ }
+ });
+ }
+
+ saveSelection() {
+ const sel = window.getSelection();
+ if (sel.rangeCount > 0) {
+ this.savedSelection = sel.getRangeAt(0);
+ }
+ }
+
+ restoreSelection() {
+ if (this.savedSelection) {
+ const sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(this.savedSelection);
+ }
+ }
+
+ executeCommand(command, value = null) {
+ if (command === 'togglesource') {
+ this.toggleSource();
+ return;
+ }
+
+ if (this.isSourceMode) {
+ alert('Switch to WYSIWYG mode to use formatting commands.');
+ return;
+ }
+
+ this.restoreSelection();
+ this.editableArea.focus();
+
+ switch (command) {
+ case 'heading':
+ this.formatHeading(value);
+ break;
+ case 'bold':
+ document.execCommand('bold', false, null);
+ break;
+ case 'italic':
+ document.execCommand('italic', false, null);
+ break;
+ case 'underline':
+ document.execCommand('underline', false, null);
+ break;
+ case 'strikethrough':
+ document.execCommand('strikethrough', false, null);
+ break;
+ case 'link':
+ this.showLinkPopup();
+ return;
+ case 'image':
+ this.showImagePopup();
+ return;
+ case 'blockquote':
+ this.insertBlockquote();
+ break;
+ case 'codeblock':
+ this.insertCodeBlock();
+ return;
+ case 'inlinecode':
+ this.insertInlineCode();
+ break;
+ }
+
+ this.saveSelection();
+ }
+
+ toggleSource() {
+ if (this.isSourceMode) {
+ this.setContent(this.sourceArea.value);
+ this.editableArea.style.display = 'block';
+ this.sourceArea.style.display = 'none';
+ this.isSourceMode = false;
+ } else {
+ this.sourceArea.value = this.getContent();
+ this.editableArea.style.display = 'none';
+ this.sourceArea.style.display = 'block';
+ this.isSourceMode = true;
+ }
+ }
+
+ formatHeading(tag) {
+ const selection = window.getSelection();
+ if (!selection.rangeCount) return;
+
+ const range = selection.getRangeAt(0);
+ const parent = range.commonAncestorContainer.parentElement;
+
+ if (parent.tagName && parent.tagName.match(/^H[1-6]$/)) {
+ const p = document.createElement('p');
+ p.innerHTML = parent.innerHTML;
+ parent.replaceWith(p);
+ } else {
+ document.execCommand('formatBlock', false, tag);
+ }
+ }
+
+ showLinkPopup(existingLink = null) {
+ this.closePopup();
+ this.saveSelection();
+
+ const popup = document.createElement('div');
+ popup.className = 'miku-popup';
+ popup.innerHTML = `
+ <div class="miku-popup-header">
+ <span>${existingLink ? 'Edit Link' : 'Insert Link'}</span>
+ <button class="miku-popup-close">&times;</button>
+ </div>
+ <div class="miku-popup-body">
+ <div class="miku-popup-field">
+ <label>Link Text:</label>
+ <input type="text" id="link-text" value="${existingLink ? existingLink.textContent : ''}" />
+ </div>
+ <div class="miku-popup-field">
+ <label>URL:</label>
+ <input type="text" id="link-url" value="${existingLink ? existingLink.href : 'https://'}" />
+ </div>
+ <div class="miku-popup-field">
+ <label>
+ <input type="checkbox" id="link-target" ${existingLink && existingLink.target === '_blank' ? 'checked' : ''} />
+ Open in new tab
+ </label>
+ </div>
+ </div>
+ <div class="miku-popup-footer">
+ <button class="miku-btn-primary" id="link-insert">${existingLink ? 'Update' : 'Insert'}</button>
+ <button class="miku-btn-secondary" id="link-cancel">Cancel</button>
+ </div>
+ `;
+
+ document.body.appendChild(popup);
+ this.currentPopup = popup;
+
+ const rect = this.wrapper.getBoundingClientRect();
+ popup.style.top = `${rect.top + 100}px`;
+ popup.style.left = `${rect.left + (rect.width / 2) - 200}px`;
+
+ popup.querySelector('.miku-popup-close').onclick = () => this.closePopup();
+ popup.querySelector('#link-cancel').onclick = () => this.closePopup();
+ popup.querySelector('#link-insert').onclick = () => {
+ const text = popup.querySelector('#link-text').value;
+ const url = popup.querySelector('#link-url').value;
+ const target = popup.querySelector('#link-target').checked ? '_blank' : '_self';
+
+ if (existingLink) {
+ existingLink.textContent = text;
+ existingLink.href = url;
+ existingLink.target = target;
+ } else {
+ this.insertLink(text, url, target);
+ }
+
+ this.closePopup();
+ };
+
+ popup.querySelector('#link-text').focus();
+ }
+
+ insertLink(text, url, target) {
+ if (!text || !url) return;
+
+ const link = document.createElement('a');
+ link.href = url;
+ link.target = target;
+ link.textContent = text;
+ link.className = 'miku-editable-link';
+
+ this.restoreSelection();
+ const selection = window.getSelection();
+ if (selection.rangeCount === 0) return;
+
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+ range.insertNode(link);
+
+ range.setStartAfter(link);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+
+ handleLinkClick(e) {
+ if (e.target.tagName === 'A' && e.target.classList.contains('miku-editable-link')) {
+ e.preventDefault();
+
+ const editIcon = document.createElement('span');
+ editIcon.className = 'miku-link-edit';
+ editIcon.innerHTML = '✎';
+ editIcon.onclick = (event) => {
+ event.stopPropagation();
+ this.showLinkPopup(e.target);
+ editIcon.remove();
+ };
+
+ e.target.parentNode.insertBefore(editIcon, e.target.nextSibling);
+
+ setTimeout(() => editIcon.remove(), 3000);
+ }
+ }
+
+ handleLinkEditing() {
+ document.querySelectorAll('.miku-link-edit').forEach(icon => icon.remove());
+ }
+
+ handleKeyDown(e) {
+ const selection = window.getSelection();
+ if (!selection.rangeCount) return;
+
+ const range = selection.getRangeAt(0);
+ const node = range.startContainer;
+ const parent = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
+
+ if (parent.closest('pre[contenteditable="true"]')) {
+ if (e.key === 'Tab') {
+ e.preventDefault();
+ document.execCommand('insertText', false, ' ');
+ }
+ return;
+ }
+
+ if (e.key === 'Enter') {
+ const code = parent.closest('code');
+ if (code && !code.closest('pre')) {
+ e.preventDefault();
+ const textNode = document.createTextNode('\u00A0');
+ code.parentNode.insertBefore(textNode, code.nextSibling);
+ const br = document.createElement('br');
+ textNode.parentNode.insertBefore(br, textNode.nextSibling);
+ const newRange = document.createRange();
+ newRange.setStartAfter(br);
+ newRange.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(newRange);
+ return;
+ }
+
+ const blockquote = parent.closest('blockquote');
+ if (blockquote && !e.shiftKey) {
+ e.preventDefault();
+ const p = document.createElement('p');
+ p.innerHTML = '<br>';
+ blockquote.insertAdjacentElement('afterend', p);
+ const newRange = document.createRange();
+ newRange.setStart(p, 0);
+ newRange.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(newRange);
+ }
+ }
+
+ if (e.key === 'ArrowRight') {
+ const code = parent.closest('code');
+ if (code && !code.closest('pre')) {
+ const isAtEnd = range.endOffset === node.length;
+ if (isAtEnd) {
+ e.preventDefault();
+ const textNode = document.createTextNode('\u00A0');
+ if (code.nextSibling) {
+ code.parentNode.insertBefore(textNode, code.nextSibling);
+ } else {
+ code.parentNode.appendChild(textNode);
+ }
+ const newRange = document.createRange();
+ newRange.setStart(textNode, 1);
+ newRange.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(newRange);
+ }
+ }
+ }
+
+ if (e.key === 'ArrowLeft') {
+ const code = parent.closest('code');
+ if (code && !code.closest('pre')) {
+ const isAtStart = range.startOffset === 0;
+ if (isAtStart) {
+ e.preventDefault();
+ const textNode = document.createTextNode('\u00A0');
+ code.parentNode.insertBefore(textNode, code);
+ const newRange = document.createRange();
+ newRange.setStart(textNode, 0);
+ newRange.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(newRange);
+ }
+ }
+ }
+
+ if (e.key === 'Backspace') {
+ if (range.collapsed && range.startOffset === 0) {
+ const prevSibling = parent.previousElementSibling;
+ if (prevSibling && prevSibling.classList.contains('miku-code-wrapper')) {
+ e.preventDefault();
+ prevSibling.remove();
+ }
+ }
+ }
+
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
+ setTimeout(() => {
+ const sel = window.getSelection();
+ if (sel.rangeCount > 0) {
+ const r = sel.getRangeAt(0);
+ const n = r.startContainer;
+ const p = n.nodeType === Node.TEXT_NODE ? n.parentElement : n;
+ const code = p.closest('code');
+ if (code && !code.closest('pre')) {
+ const textNode = document.createTextNode('\u00A0');
+ if (e.key === 'ArrowDown') {
+ if (code.nextSibling) {
+ code.parentNode.insertBefore(textNode, code.nextSibling);
+ } else {
+ code.parentNode.appendChild(textNode);
+ }
+ } else {
+ code.parentNode.insertBefore(textNode, code);
+ }
+ const newRange = document.createRange();
+ newRange.setStart(textNode, 0);
+ newRange.collapse(true);
+ sel.removeAllRanges();
+ sel.addRange(newRange);
+ }
+ }
+ }, 0);
+ }
+ }
+
+ showImagePopup() {
+ this.closePopup();
+ this.saveSelection();
+
+ const popup = document.createElement('div');
+ popup.className = 'miku-popup';
+ popup.innerHTML = `
+ <div class="miku-popup-header">
+ <span>Insert Image</span>
+ <button class="miku-popup-close">&times;</button>
+ </div>
+ <div class="miku-popup-body">
+ <div class="miku-popup-field">
+ <label>Image URL:</label>
+ <input type="text" id="image-url" value="https://" />
+ </div>
+ <div class="miku-popup-field">
+ <label>Display:</label>
+ <select id="image-display">
+ <option value="inline">Inline</option>
+ <option value="block">Block</option>
+ </select>
+ </div>
+ </div>
+ <div class="miku-popup-footer">
+ <button class="miku-btn-primary" id="image-insert">Insert</button>
+ <button class="miku-btn-secondary" id="image-cancel">Cancel</button>
+ </div>
+ `;
+
+ document.body.appendChild(popup);
+ this.currentPopup = popup;
+
+ const rect = this.wrapper.getBoundingClientRect();
+ popup.style.top = `${rect.top + 100}px`;
+ popup.style.left = `${rect.left + (rect.width / 2) - 200}px`;
+
+ popup.querySelector('.miku-popup-close').onclick = () => this.closePopup();
+ popup.querySelector('#image-cancel').onclick = () => this.closePopup();
+ popup.querySelector('#image-insert').onclick = () => {
+ const url = popup.querySelector('#image-url').value;
+ const display = popup.querySelector('#image-display').value;
+
+ if (url && url !== 'https://') {
+ const link = document.createElement('a');
+ link.href = url;
+ link.target = '_blank';
+
+ const img = document.createElement('img');
+ img.src = url;
+ img.alt = 'Image';
+ if (display === 'block') img.className = 'block';
+
+ link.appendChild(img);
+
+ this.restoreSelection();
+ const selection = window.getSelection();
+ if (selection.rangeCount > 0) {
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+ range.insertNode(link);
+ range.setStartAfter(link);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ } else {
+ this.editableArea.appendChild(link);
+ }
+
+ this.closePopup();
+ }
+ };
+
+ popup.querySelector('#image-url').focus();
+ }
+
+ insertBlockquote() {
+ const selection = window.getSelection();
+ if (!selection.rangeCount) return;
+
+ const range = selection.getRangeAt(0);
+ const parent = range.commonAncestorContainer.parentElement;
+
+ if (parent.closest('blockquote')) {
+ const blockquote = parent.closest('blockquote');
+ const p = document.createElement('p');
+ p.innerHTML = blockquote.innerHTML;
+ blockquote.replaceWith(p);
+ } else {
+ document.execCommand('formatBlock', false, 'blockquote');
+ }
+ }
+
+ insertCodeBlock() {
+ this.closePopup();
+
+ const codeWrapper = document.createElement('div');
+ codeWrapper.className = 'miku-code-wrapper';
+ codeWrapper.contentEditable = 'false';
+
+ const langSelect = document.createElement('select');
+ langSelect.className = 'miku-code-lang';
+ langSelect.innerHTML = LANGUAGES.map(lang =>
+ `<option value="${lang}">${lang}</option>`
+ ).join('');
+
+ const pre = document.createElement('pre');
+ pre.setAttribute('data-language', 'python');
+ pre.contentEditable = 'true';
+ pre.textContent = '// Your code here...';
+
+ langSelect.onchange = () => {
+ pre.setAttribute('data-language', langSelect.value);
+ };
+
+ codeWrapper.appendChild(langSelect);
+ codeWrapper.appendChild(pre);
+
+ this.restoreSelection();
+ const selection = window.getSelection();
+ if (selection.rangeCount > 0) {
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+ range.insertNode(codeWrapper);
+
+ const p = document.createElement('p');
+ p.innerHTML = '<br>';
+ codeWrapper.insertAdjacentElement('afterend', p);
+
+ setTimeout(() => {
+ pre.focus();
+ const newRange = document.createRange();
+ newRange.selectNodeContents(pre);
+ const sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(newRange);
+ }, 50);
+ } else {
+ this.editableArea.appendChild(codeWrapper);
+ const p = document.createElement('p');
+ p.innerHTML = '<br>';
+ this.editableArea.appendChild(p);
+
+ setTimeout(() => pre.focus(), 50);
+ }
+ }
+
+ insertInlineCode() {
+ const selection = window.getSelection();
+ const selectedText = selection.toString();
+
+ if (selectedText) {
+ const code = document.createElement('code');
+ code.textContent = selectedText;
+
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+ range.insertNode(code);
+ } else {
+ const code = document.createElement('code');
+ code.textContent = 'code';
+ this.insertNodeAtCursor(code);
+ }
+ }
+
+ insertNodeAtCursor(node) {
+ const selection = window.getSelection();
+ if (!selection.rangeCount) {
+ this.editableArea.appendChild(node);
+ return;
+ }
+
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+ range.insertNode(node);
+
+ range.setStartAfter(node);
+ range.setEndAfter(node);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+
+ ensureParagraphStructure() {
+ const children = Array.from(this.editableArea.childNodes);
+ children.forEach(child => {
+ if (child.nodeType === Node.TEXT_NODE && child.textContent.trim() !== '') {
+ const p = document.createElement('p');
+ p.textContent = child.textContent;
+ child.replaceWith(p);
+ }
+ });
+
+ if (this.editableArea.children.length === 0) {
+ const p = document.createElement('p');
+ p.innerHTML = '<br>';
+ this.editableArea.appendChild(p);
+ }
+ }
+
+ handlePaste(e) {
+ e.preventDefault();
+
+ const text = e.clipboardData.getData('text/plain');
+ const selection = window.getSelection();
+
+ if (!selection.rangeCount) return;
+
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+
+ const lines = text.split('\n');
+ lines.forEach((line, index) => {
+ const p = document.createElement('p');
+ p.textContent = line || '\u00A0';
+ range.insertNode(p);
+
+ if (index < lines.length - 1) {
+ range.setStartAfter(p);
+ range.setEndAfter(p);
+ }
+ });
+ }
+
+ closePopup() {
+ if (this.currentPopup) {
+ this.currentPopup.remove();
+ this.currentPopup = null;
+ }
+ }
+
+ getContent() {
+ if (this.isSourceMode) {
+ return this.sourceArea.value;
+ }
+
+ const clone = this.editableArea.cloneNode(true);
+ clone.querySelectorAll('.miku-code-wrapper').forEach(wrapper => {
+ const pre = wrapper.querySelector('pre');
+ const cleanPre = pre.cloneNode(true);
+ cleanPre.removeAttribute('contenteditable');
+ wrapper.replaceWith(cleanPre);
+ });
+
+ clone.querySelectorAll('.miku-link-edit').forEach(el => el.remove());
+
+ return clone.innerHTML;
+ }
+
+ setContent(html) {
+ if (this.isSourceMode) {
+ this.sourceArea.value = html;
+ } else {
+ this.editableArea.innerHTML = html || '<p><br></p>';
+
+ this.editableArea.querySelectorAll('pre[data-language]').forEach(pre => {
+ if (!pre.parentElement.classList.contains('miku-code-wrapper')) {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'miku-code-wrapper';
+ wrapper.contentEditable = 'false';
+
+ const langSelect = document.createElement('select');
+ langSelect.className = 'miku-code-lang';
+ langSelect.innerHTML = LANGUAGES.map(lang =>
+ `<option value="${lang}" ${lang === pre.getAttribute('data-language') ? 'selected' : ''}>${lang}</option>`
+ ).join('');
+
+ langSelect.onchange = () => {
+ pre.setAttribute('data-language', langSelect.value);
+ };
+
+ pre.contentEditable = 'true';
+ pre.parentNode.insertBefore(wrapper, pre);
+ wrapper.appendChild(langSelect);
+ wrapper.appendChild(pre);
+ }
+ });
+ }
+ }
+
+ clear() {
+ this.editableArea.innerHTML = '<p><br></p>';
+ this.sourceArea.value = '';
+ }
+
+ destroy() {
+ this.closePopup();
+ this.container.innerHTML = '';
+ }
+ }
+
+ window.FancyMiku = FancyMiku;
+})();