mirror of https://github.com/Meekdai/Gmeek.git
540 lines
17 KiB
HTML
540 lines
17 KiB
HTML
{% extends 'base.html' %}
|
||
|
||
{% block head %}
|
||
<meta name="description" content="{{ blogBase['description'] }}">
|
||
<meta property="og:title" content="{{ blogBase['postTitle'] }}">
|
||
<meta property="og:description" content="{{ blogBase['description'] }}">
|
||
<meta property="og:type" content="article">
|
||
<meta property="og:url" content="{{ blogBase['postUrl'] }}">
|
||
<meta property="og:image" content="{{ blogBase['ogImage'] }}">
|
||
<title>{{ blogBase['postTitle'] }}</title>
|
||
{% if blogBase['highlight']==1 %}<link href="//unpkg.com/@wooorm/starry-night@2.1.1/style/both.css" rel="stylesheet" />{% endif %}
|
||
{{ blogBase['head'] }}
|
||
{% endblock %}
|
||
|
||
{% block style %}
|
||
<style>
|
||
.postTitle{margin: auto 0;font-size:40px;font-weight:bold;}
|
||
.title-right{display:flex;margin:auto 0 0 auto;}
|
||
.title-right .circle{padding: 14px 16px;margin-right:8px;}
|
||
#postBody{border-bottom: 1px solid var(--color-border-default);padding-bottom:36px;}
|
||
#postBody hr{height:2px;}
|
||
#postBody .paragraph {
|
||
display: block;
|
||
position: relative;
|
||
padding-right: 30px;
|
||
cursor: pointer;
|
||
user-select: text;
|
||
}
|
||
#postBody .paragraph.highlighted {
|
||
background-color: #ffffcc;
|
||
}
|
||
#postBody .paragraph .note-text {
|
||
display: block;
|
||
margin-top: 4px;
|
||
font-size: 0.9em;
|
||
color: #333;
|
||
background: #fffae6;
|
||
border-left: 3px solid #f0c000;
|
||
padding: 2px 6px;
|
||
user-select: text;
|
||
}
|
||
#cmButton{height:48px;margin-top:48px;}
|
||
#comments{margin-top:64px;}
|
||
.g-emoji{font-size:24px;}
|
||
@media (max-width: 600px) {
|
||
body {padding: 8px;}
|
||
.postTitle{font-size:24px;}
|
||
}
|
||
{% if blogBase['highlight']!=0 -%}
|
||
.copy-feedback {
|
||
display: none;
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 50px;
|
||
color: var(--color-fg-on-emphasis);
|
||
background-color: var(--color-fg-muted);
|
||
border-radius: 3px;
|
||
padding: 5px 8px;
|
||
font-size: 12px;
|
||
}{% endif %}
|
||
|
||
.highlighted-sentence {
|
||
background: yellow;
|
||
transition: background 0.3s ease;
|
||
}
|
||
#backToTopBtn {
|
||
position: fixed;
|
||
bottom: 40px;
|
||
right: 30px;
|
||
z-index: 999;
|
||
background-color: #007bff;
|
||
color: white;
|
||
border: none;
|
||
outline: none;
|
||
padding: 10px 14px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
font-size: 20px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
#backToTopBtn:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
</style>
|
||
{{ blogBase['style'] }}
|
||
|
||
{% endblock %}
|
||
{% block header %}
|
||
<h1 class="postTitle">{{ blogBase['postTitle'] }}</h1>
|
||
<div class="title-right">
|
||
<a href="{{ blogBase['homeUrl'] }}" id="buttonHome" class="btn btn-invisible circle" title="{{ i18n['home'] }}">
|
||
<svg class="octicon" width="16" height="16">
|
||
<path id="pathHome" fill-rule="evenodd"></path>
|
||
</svg>
|
||
</a>
|
||
{% if blogBase['showPostSource']==1 %}
|
||
<a href="{{ blogBase['postSourceUrl'] }}" target="_blank" class="btn btn-invisible circle" title="Issue">
|
||
<svg class="octicon" width="16" height="16">
|
||
<path id="pathIssue" fill-rule="evenodd"></path>
|
||
</svg>
|
||
</a>
|
||
{% endif %}
|
||
|
||
<a class="btn btn-invisible circle" onclick="modeSwitch();" title="{{ i18n['switchTheme'] }}" {%- if blogBase['themeMode']=='fix' -%}style="display:none;"{%- endif -%}>
|
||
<svg class="octicon" width="16" height="16" >
|
||
<path id="themeSwitch" fill-rule="evenodd"></path>
|
||
</svg>
|
||
</a>
|
||
|
||
<a id="ttsBtn" class="btn btn-invisible circle" title="Read">
|
||
<svg class="octicon" width="16" height="16" >
|
||
<path d="M4 4 v8 l6 -4 -6 -4 z" fill-rule="evenodd"></path>
|
||
</svg>
|
||
</a>
|
||
<a id="exportNotesBtn" class="btn btn-invisible circle" title="导出笔记" href="javascript:void(0)">
|
||
<svg width="16" height="16" class="octicon">
|
||
<path d="M5 3v2h6V3h2v2h1a1 1 0 011 1v2H2V6a1 1 0 011-1h1V3h2zm-3 6h14v2a1 1 0 01-1 1H3a1 1 0 01-1-1V9zm0 4h14v2H2v-2z"></path>
|
||
</svg>
|
||
</a>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="markdown-body" id="postBody">{{ blogBase['postBody'] }}</div>
|
||
<div style="font-size:small;margin-top:8px;float:right;">{{ blogBase['bottomText'] }}</div>
|
||
{% if blogBase['needComment']==1 %}
|
||
<button class="btn btn-block" type="button" onclick="openComments()" id="cmButton">{{ i18n['comments'] }}</button>
|
||
<div class="comments" id="comments"></div>
|
||
{% endif %}
|
||
|
||
<button id="backToTopBtn" title="返回顶部" style="display: none;">
|
||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||
<path fill="currentColor" d="M12 8l6 6H6z"/>
|
||
</svg>
|
||
</button>
|
||
{% endblock %}
|
||
|
||
{% block script %}
|
||
<script>
|
||
document.getElementById("pathHome").setAttribute("d",IconList["home"]);
|
||
//document.getElementById("read").setAttribute("d",IconList["startread"]);
|
||
console.log(IconList["startread"]);
|
||
{% if blogBase['showPostSource']==1 %}document.getElementById("pathIssue").setAttribute("d",IconList["github"]);{% endif %}
|
||
|
||
{% if blogBase['commentNum']>0 -%}
|
||
cmButton=document.getElementById("cmButton");
|
||
span=document.createElement("span");
|
||
span.setAttribute("class","Counter");
|
||
span.innerHTML="{{ blogBase['commentNum'] }}";
|
||
cmButton.appendChild(span);
|
||
{%- endif %}
|
||
|
||
{% if blogBase['needComment']==1 %}
|
||
function openComments(){
|
||
cm=document.getElementById("comments");
|
||
cmButton=document.getElementById("cmButton");
|
||
cmButton.disabled=true;
|
||
cmButton.innerHTML="loading";
|
||
span=document.createElement("span");
|
||
span.setAttribute("class","AnimatedEllipsis");
|
||
cmButton.appendChild(span);
|
||
|
||
script=document.createElement("script");
|
||
script.setAttribute("src","https://utteranc.es/client.js");
|
||
script.setAttribute("repo","{{ blogBase['repoName'] }}");
|
||
script.setAttribute("issue-term","title");
|
||
{% if blogBase['themeMode']=='manual' %}
|
||
if(localStorage.getItem("meek_theme")=="dark"){script.setAttribute("theme","dark-blue");}
|
||
else if(localStorage.getItem("meek_theme")=="light") {script.setAttribute("theme","github-light");}
|
||
else{script.setAttribute("theme","preferred-color-scheme");}
|
||
{% else %}
|
||
script.setAttribute("theme","{{ blogBase['nightTheme'] }}");
|
||
{% endif %}
|
||
script.setAttribute("crossorigin","anonymous");
|
||
script.setAttribute("async","");
|
||
cm.appendChild(script);
|
||
|
||
int=self.setInterval("iFrameLoading()",200);
|
||
}
|
||
|
||
function iFrameLoading(){
|
||
var utterances=document.getElementsByClassName('utterances');
|
||
if(utterances.length==1){
|
||
if(utterances[0].style.height!=""){
|
||
utterancesLoad=1;
|
||
int=window.clearInterval(int);
|
||
document.getElementById("cmButton").style.display="none";
|
||
console.log("utterances Load OK");
|
||
}
|
||
}
|
||
}
|
||
{%- endif %}
|
||
document.getElementById('exportNotesBtn')?.addEventListener('click', () => {
|
||
const notesData = JSON.parse(localStorage.getItem('myParagraphNotes') || '{}');
|
||
if (!Object.keys(notesData).length) {
|
||
alert('你还没有收藏任何段落');
|
||
return;
|
||
}
|
||
|
||
let exportText = '我的段落笔记:\n\n';
|
||
|
||
for (const [pid, note] of Object.entries(notesData)) {
|
||
const paraElem = document.getElementById(pid);
|
||
if (!paraElem) continue;
|
||
const paraText = paraElem.cloneNode(true);
|
||
paraText.querySelectorAll('.note-text').forEach(n => n.remove());
|
||
const pureText = paraText.innerText.replace(/\n/g, ' ');
|
||
exportText += `段落 ${pid.replace('para-', '')}:\n${pureText}\n笔记:${note}\n\n`;
|
||
}
|
||
|
||
const blob = new Blob([exportText], { type: 'text/plain;charset=utf-8' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = '我的段落笔记.txt';
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
});
|
||
|
||
(function() {
|
||
const STORAGE_KEY = 'postScrollPosition';
|
||
window.addEventListener('load', () => {
|
||
const savedPos = localStorage.getItem(STORAGE_KEY);
|
||
if (savedPos) {
|
||
window.scrollTo(0, parseInt(savedPos, 10));
|
||
}
|
||
});
|
||
|
||
let saveTimeout = null;
|
||
window.addEventListener('scroll', () => {
|
||
if (saveTimeout) return;
|
||
saveTimeout = setTimeout(() => {
|
||
localStorage.setItem(STORAGE_KEY, window.scrollY);
|
||
saveTimeout = null;
|
||
}, 200);
|
||
});
|
||
})();
|
||
|
||
let originalHTML = "";
|
||
(() => {
|
||
const ttsBtn = document.getElementById('ttsBtn');
|
||
const ttsRateSelect = document.getElementById('ttsRate');
|
||
const postBody = document.getElementById('postBody');
|
||
let utterance = null;
|
||
let isSpeaking = false;
|
||
let sentenceSpans = [];
|
||
let currentIndex = 0;
|
||
|
||
function resetTTSButton() {
|
||
ttsBtn.title = "朗读/暂停";
|
||
ttsBtn.querySelector('path').setAttribute('d', "M4 4v8l6-4-6-4z"); // 播放图标
|
||
}
|
||
|
||
function getCleanTextFromPostBody() {
|
||
const postBody = document.getElementById('postBody');
|
||
const clone = postBody.cloneNode(true);
|
||
clone.querySelectorAll('mark, .highlight, [data-note="true"]').forEach(el => {
|
||
const textNode = document.createTextNode(el.innerText || el.textContent || '');
|
||
el.replaceWith(textNode);
|
||
});
|
||
|
||
return clone.innerText.trim();
|
||
}
|
||
function clearHighlights() {
|
||
sentenceSpans.forEach(span => span.classList.remove("highlighted-sentence"));
|
||
}
|
||
|
||
function scrollIntoViewSmoothly(el) {
|
||
el.scrollIntoView({
|
||
behavior: "smooth",
|
||
block: "center"
|
||
});
|
||
}
|
||
|
||
function splitTextIntoSentences(text) {
|
||
return text.match(/[^。!?]+[。!?]?/g) || [text];
|
||
}
|
||
|
||
function prepareSentences() {
|
||
const postBody = document.getElementById('postBody');
|
||
const text = Array.from(document.querySelectorAll('#postBody .paragraph'))
|
||
.map(p => {
|
||
// 拿掉 .note-text 的内容
|
||
const clone = p.cloneNode(true);
|
||
clone.querySelectorAll('.note-text').forEach(n => n.remove());
|
||
return clone.innerText.trim();
|
||
})
|
||
.filter(Boolean)
|
||
.join('\n');
|
||
const sentences = splitTextIntoSentences(text);
|
||
originalHTML = postBody.innerHTML;
|
||
postBody.innerHTML = ""; // 清空内容
|
||
sentenceSpans = sentences.map(s => {
|
||
const span = document.createElement("span");
|
||
span.textContent = s;
|
||
span.style.display = "inline";
|
||
postBody.appendChild(span);
|
||
postBody.appendChild(document.createTextNode(" ")); // 保持间距
|
||
return span;
|
||
});
|
||
return sentences;
|
||
}
|
||
|
||
function speakSentences(sentences) {
|
||
if (!window.speechSynthesis) return alert("不支持语音合成");
|
||
if (!sentences.length) return;
|
||
|
||
currentIndex = 0;
|
||
isSpeaking = true;
|
||
|
||
function speakNext() {
|
||
if (currentIndex >= sentences.length) {
|
||
isSpeaking = false;
|
||
resetTTSButton();
|
||
postBody.innerHTML = originalHTML;
|
||
return;
|
||
}
|
||
|
||
const text = sentences[currentIndex];
|
||
utterance = new SpeechSynthesisUtterance(text);
|
||
utterance.lang = "zh-CN";
|
||
utterance.rate = parseFloat(ttsRateSelect?.value || 1);
|
||
|
||
utterance.onstart = () => {
|
||
clearHighlights();
|
||
const span = sentenceSpans[currentIndex];
|
||
if (span) {
|
||
span.classList.add("highlighted-sentence");
|
||
scrollIntoViewSmoothly(span);
|
||
}
|
||
};
|
||
|
||
utterance.onend = () => {
|
||
currentIndex++;
|
||
speakNext();
|
||
};
|
||
|
||
utterance.onerror = () => {
|
||
isSpeaking = false;
|
||
resetTTSButton();
|
||
alert("朗读出错");
|
||
};
|
||
|
||
speechSynthesis.speak(utterance);
|
||
}
|
||
|
||
speakNext();
|
||
}
|
||
|
||
ttsBtn.addEventListener("click", () => {
|
||
if (!window.speechSynthesis) {
|
||
alert('你的浏览器不支持语音合成功能');
|
||
return;
|
||
}
|
||
|
||
if (isSpeaking) {
|
||
if (speechSynthesis.paused) {
|
||
speechSynthesis.resume();
|
||
ttsBtn.title = "暂停朗读";
|
||
ttsBtn.querySelector('path').setAttribute('d', "M6 4h2v8H6zM10 4h2v8h-2z");
|
||
} else {
|
||
speechSynthesis.pause();
|
||
ttsBtn.title = "继续朗读";
|
||
ttsBtn.querySelector('path').setAttribute('d', "M4 4v8l6-4-6-4z");
|
||
}
|
||
} else {
|
||
speechSynthesis.cancel();
|
||
clearHighlights();
|
||
const sentences = prepareSentences();
|
||
speakSentences(sentences);
|
||
ttsBtn.title = "暂停朗读";
|
||
ttsBtn.querySelector('path').setAttribute('d', "M6 4h2v8H6zM10 4h2v8h-2z");
|
||
}
|
||
});
|
||
})();
|
||
|
||
|
||
(function() {
|
||
const STORAGE_KEY = 'myParagraphNotes';
|
||
const postBody = document.getElementById('postBody');
|
||
|
||
if (!postBody) return;
|
||
|
||
const text = postBody.innerText || postBody.textContent;
|
||
const lines = text.split(/\n+/).filter(line => line.trim().length > 0);
|
||
|
||
postBody.innerHTML = '';
|
||
|
||
lines.forEach((line, index) => {
|
||
const span = document.createElement('span');
|
||
span.className = 'paragraph';
|
||
span.id = 'para-' + index;
|
||
span.textContent = line.trim();
|
||
postBody.appendChild(span);
|
||
});
|
||
|
||
let notesData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
|
||
|
||
|
||
function saveNotes() {
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(notesData));
|
||
}
|
||
|
||
function renderParagraph(p) {
|
||
const pid = p.id;
|
||
const oldNote = p.querySelector('.note-text');
|
||
if (oldNote) oldNote.remove();
|
||
|
||
if (notesData[pid]) {
|
||
p.classList.add('highlighted');
|
||
const noteElem = document.createElement('div');
|
||
noteElem.className = 'note-text';
|
||
noteElem.textContent = notesData[pid];
|
||
noteElem.style.fontSize = '0.9em';
|
||
noteElem.style.color = '#888';
|
||
noteElem.style.marginTop = '4px';
|
||
noteElem.setAttribute('data-note', 'true');
|
||
p.appendChild(noteElem);
|
||
} else {
|
||
p.classList.remove('highlighted');
|
||
}
|
||
}
|
||
|
||
function setupParagraph(p) {
|
||
p.addEventListener('click', () => {
|
||
const pid = p.id;
|
||
const currentNote = notesData[pid] || '';
|
||
const userInput = prompt('编辑笔记(留空将删除):', currentNote);
|
||
|
||
if (userInput === null) return; // 取消
|
||
|
||
if (userInput.trim() === '') {
|
||
delete notesData[pid];
|
||
} else {
|
||
notesData[pid] = userInput.trim();
|
||
}
|
||
|
||
renderParagraph(p);
|
||
saveNotes();
|
||
});
|
||
}
|
||
|
||
document.querySelectorAll('#postBody .paragraph').forEach(p => {
|
||
renderParagraph(p);
|
||
setupParagraph(p);
|
||
});
|
||
})();
|
||
|
||
window.addEventListener('scroll', () => {
|
||
const btn = document.getElementById('backToTopBtn');
|
||
if (document.documentElement.scrollTop > 300 || document.body.scrollTop > 300) {
|
||
btn.style.display = 'block';
|
||
} else {
|
||
btn.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
document.getElementById('backToTopBtn').addEventListener('click', () => {
|
||
window.scrollTo({
|
||
top: 0,
|
||
behavior: 'smooth'
|
||
});
|
||
});
|
||
|
||
{% if blogBase['highlight']!=0 -%}
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const createClipboardHTML = (codeContent, additionalClasses = '') => `
|
||
<pre class="notranslate"><code class="notranslate">${codeContent}</code></pre>
|
||
<div class="clipboard-container position-absolute right-0 top-0 ${additionalClasses}">
|
||
<clipboard-copy class="ClipboardButton btn m-2 p-0" role="button" style="display: inherit;">
|
||
<svg height="16" width="16" class="octicon octicon-copy m-2"><path d="${IconList["copy"]}"></path></svg>
|
||
<svg height="16" width="16" class="octicon octicon-check color-fg-success m-2 d-none"><path d="${IconList["check"]}"></path></svg>
|
||
</clipboard-copy>
|
||
<div class="copy-feedback">Copied!</div>
|
||
</div>
|
||
`;
|
||
|
||
const handleCodeElements = (selector = '') => {
|
||
document.querySelectorAll(selector).forEach(codeElement => {
|
||
const codeContent = codeElement.innerHTML;
|
||
const newStructure = document.createElement('div');
|
||
newStructure.className = 'snippet-clipboard-content position-relative overflow-auto';
|
||
newStructure.innerHTML = createClipboardHTML(codeContent);
|
||
|
||
const parentElement = codeElement.parentElement;
|
||
if (selector.includes('highlight')) {
|
||
parentElement.insertBefore(newStructure, codeElement.nextSibling);
|
||
parentElement.removeChild(codeElement);
|
||
} else {
|
||
parentElement.parentElement.replaceChild(newStructure, parentElement);
|
||
}
|
||
});
|
||
};
|
||
|
||
handleCodeElements('pre.notranslate > code.notranslate');
|
||
handleCodeElements('div.highlight > pre.notranslate');
|
||
|
||
let currentFeedback = null;
|
||
document.querySelectorAll('clipboard-copy').forEach(copyButton => {
|
||
copyButton.addEventListener('click', () => {
|
||
const codeContent = copyButton.closest('.snippet-clipboard-content').innerText;
|
||
const tempTextArea = document.createElement('textarea');
|
||
tempTextArea.value = codeContent;
|
||
document.body.appendChild(tempTextArea);
|
||
tempTextArea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(tempTextArea);
|
||
|
||
const copyIcon = copyButton.querySelector('.octicon-copy');
|
||
const checkIcon = copyButton.querySelector('.octicon-check');
|
||
const copyFeedback = copyButton.nextElementSibling;
|
||
|
||
if (currentFeedback && currentFeedback !== copyFeedback) {currentFeedback.style.display = 'none';}
|
||
currentFeedback = copyFeedback;
|
||
|
||
copyIcon.classList.add('d-none');
|
||
checkIcon.classList.remove('d-none');
|
||
copyFeedback.style.display = 'block';
|
||
copyButton.style.borderColor = 'var(--color-success-fg)';
|
||
|
||
setTimeout(() => {
|
||
copyIcon.classList.remove('d-none');
|
||
checkIcon.classList.add('d-none');
|
||
copyFeedback.style.display = 'none';
|
||
copyButton.style.borderColor = '';
|
||
}, 2000);
|
||
});
|
||
});
|
||
});
|
||
{%- endif %}
|
||
|
||
</script>
|
||
|
||
{{ blogBase['script'] }}
|
||
{% endblock %}
|