feat: 🎸 support carousel in md
This commit is contained in:
@@ -5,6 +5,7 @@ import sup from "markdown-it-sup";
|
||||
import sub from "markdown-it-sub";
|
||||
import mark from "markdown-it-mark";
|
||||
import ins from "markdown-it-ins";
|
||||
import carousel from "./lib/markdown-it-carousel";
|
||||
|
||||
// import markdownit from 'markdown-it'
|
||||
|
||||
@@ -94,7 +95,8 @@ export default withMermaid(
|
||||
link: "/sounds-of-american-english/0-intro",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "1. 基础",
|
||||
{
|
||||
text: "1. 基础",
|
||||
link: "/sounds-of-american-english/1-basics",
|
||||
items: [
|
||||
{
|
||||
@@ -104,11 +106,11 @@ export default withMermaid(
|
||||
{
|
||||
text: "1.2. 英文字母",
|
||||
link: "/sounds-of-american-english/1.2-alphabets",
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "2. 发声器官",
|
||||
text: "2. 发声器官",
|
||||
link: "/sounds-of-american-english/2-articulators",
|
||||
},
|
||||
{
|
||||
@@ -149,7 +151,7 @@ export default withMermaid(
|
||||
text: "3.1.7. aɪ... oʊ",
|
||||
link: "/sounds-of-american-english/3.1.7-aɪ",
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "3.2. 辅音",
|
||||
@@ -207,7 +209,7 @@ export default withMermaid(
|
||||
{
|
||||
text: "3.2.13. w, j",
|
||||
link: "/sounds-of-american-english/3.2.13-wj",
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "3.2.14. h",
|
||||
link: "/sounds-of-american-english/3.2.14-h",
|
||||
@@ -272,7 +274,7 @@ export default withMermaid(
|
||||
text: "6.4. 常见词根词缀",
|
||||
link: "/sounds-of-american-english/6.4-parts-of-words",
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "7. 从此之后",
|
||||
@@ -423,8 +425,8 @@ export default withMermaid(
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
"/enjoy-app/": [
|
||||
@@ -482,7 +484,6 @@ export default withMermaid(
|
||||
link: "/intro",
|
||||
},
|
||||
],
|
||||
|
||||
},
|
||||
|
||||
socialLinks: [
|
||||
@@ -494,7 +495,7 @@ export default withMermaid(
|
||||
},
|
||||
|
||||
sitemap: {
|
||||
hostname: 'https://1000h.org'
|
||||
hostname: "https://1000h.org",
|
||||
},
|
||||
|
||||
lastUpdated: true,
|
||||
@@ -509,10 +510,11 @@ export default withMermaid(
|
||||
md.use(sup);
|
||||
md.use(mark);
|
||||
md.use(ins);
|
||||
md.use(carousel);
|
||||
},
|
||||
toc: {
|
||||
level: [1, 2, 3]
|
||||
}
|
||||
level: [1, 2, 3],
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
46
1000-hours/.vitepress/lib/markdown-it-carousel.ts
Normal file
46
1000-hours/.vitepress/lib/markdown-it-carousel.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type MarkdownIt from "markdown-it/lib/index.mjs";
|
||||
import * as cheerio from "cheerio";
|
||||
|
||||
export default function carouselPlugin(md: MarkdownIt) {
|
||||
const html_block = md.renderer.rules.html_block!;
|
||||
|
||||
md.renderer.rules.html_block = (...args) => {
|
||||
const [tokens, idx] = args;
|
||||
const token = tokens[idx];
|
||||
|
||||
try {
|
||||
if (token.content.match(/class=["']carousel["']/)) {
|
||||
const $ = cheerio.load(token.content, null, false);
|
||||
const carousel = $(".carousel");
|
||||
|
||||
if (carousel) {
|
||||
const imgs = Array.from($(".carousel img"))
|
||||
.map((item) => {
|
||||
const src = $(item).attr("src");
|
||||
return `<swiper-slide><img src="${src}" /></swiper-slide>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const template = `
|
||||
<div class="carousel">
|
||||
<swiper-container class="carousel-inner" thumbs-swiper=".swiper-thumb" space-between="10" navigation="true" pagination="true">
|
||||
${imgs}
|
||||
</swiper-container>
|
||||
<swiper-container class="swiper-thumb" slides-per-view="4" free-mode="true" space-between="10">
|
||||
${imgs}
|
||||
</swiper-container>
|
||||
</div>
|
||||
`;
|
||||
|
||||
carousel.replaceWith(template);
|
||||
|
||||
return $.html();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("convert carousel element error");
|
||||
}
|
||||
|
||||
return html_block(...args);
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="speak-word">
|
||||
<div class="word" >
|
||||
<div class="word">
|
||||
{{ props.word }}
|
||||
</div>
|
||||
<div v-if="pos" class="pos">
|
||||
@@ -8,19 +8,32 @@
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<div class="ctrl">
|
||||
<div class="ctrl-part" :class="item.label" v-for="item, ix in audios" :key="`audio-${ix}`">
|
||||
<div
|
||||
class="ctrl-part"
|
||||
:class="item.label"
|
||||
v-for="(item, ix) in audios"
|
||||
:key="`audio-${ix}`"
|
||||
>
|
||||
<div v-if="ix !== 0" class="divider"></div>
|
||||
<button class="play-button" :class="item.label" @click="playAudio(item.label)">
|
||||
<button
|
||||
class="play-button"
|
||||
:class="item.label"
|
||||
@click="playAudio(item.label)"
|
||||
>
|
||||
<span class="accent-label">{{ item.label }}</span>
|
||||
<img :src="svgUrl(item.label)" class="icon" alt="sound" />
|
||||
</button>
|
||||
<audio class="audio" :class="item.label" :src="item.audio" controls="false" ></audio>
|
||||
<audio
|
||||
class="audio"
|
||||
:class="item.label"
|
||||
:src="item.audio"
|
||||
controls="false"
|
||||
></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { getAudioPath } from "../data";
|
||||
@@ -44,56 +57,55 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const svgUrl = (accent) => {
|
||||
if (accent === 'uk') {
|
||||
return '/images/speaker-brown.svg';
|
||||
} else if (accent === 'us') {
|
||||
return '/images/speaker-blue.svg';
|
||||
if (accent === "uk") {
|
||||
return "/images/speaker-brown.svg";
|
||||
} else if (accent === "us") {
|
||||
return "/images/speaker-blue.svg";
|
||||
}
|
||||
return '/images/speaker-black.svg';
|
||||
}
|
||||
return "/images/speaker-black.svg";
|
||||
};
|
||||
|
||||
const audioPathUS = computed(() => {
|
||||
if (props.audioUs) {
|
||||
return props.audioUs;
|
||||
}
|
||||
return getAudioPath(props.word, "us")
|
||||
return getAudioPath(props.word, "us");
|
||||
});
|
||||
|
||||
const audioPathUK = computed(() => {
|
||||
if (props.audioUk) {
|
||||
return props.audioUk;
|
||||
}
|
||||
return getAudioPath(props.word, "uk")
|
||||
return getAudioPath(props.word, "uk");
|
||||
});
|
||||
|
||||
const audioPathOther = computed(() => {
|
||||
if (props.audioUk) {
|
||||
return props.audioUk;
|
||||
}
|
||||
return getAudioPath(props.word, "other")
|
||||
return getAudioPath(props.word, "other");
|
||||
});
|
||||
|
||||
const audios = computed(() => {
|
||||
const ret:any = [];
|
||||
const ret: any = [];
|
||||
if (audioPathUS.value) {
|
||||
ret.push({ label: 'us', audio: audioPathUS.value});
|
||||
ret.push({ label: "us", audio: audioPathUS.value });
|
||||
}
|
||||
if (audioPathUK.value) {
|
||||
ret.push({ label: 'uk', audio: audioPathUK.value});
|
||||
ret.push({ label: "uk", audio: audioPathUK.value });
|
||||
}
|
||||
if (audioPathOther.value) {
|
||||
ret.push({ label: 'other', audio: audioPathOther.value});
|
||||
ret.push({ label: "other", audio: audioPathOther.value });
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
function playAudio(accent) {
|
||||
const audioEl:any = document.querySelector(`audio.${accent}`);
|
||||
const audioEl: any = document.querySelector(`audio.${accent}`);
|
||||
audioEl.play();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import url(./SpeakWord.scss);
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import { h } from 'vue'
|
||||
import type { Theme } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import SpeakWord from './components/SpeakWord.vue'
|
||||
import MyLayout from './layouts/index.vue'
|
||||
import './style.scss'
|
||||
import { h } from "vue";
|
||||
import type { Theme } from "vitepress";
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import SpeakWord from "./components/SpeakWord.vue";
|
||||
import MyLayout from "./layouts/index.vue";
|
||||
import "./style.scss";
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
@@ -16,6 +16,6 @@ export default {
|
||||
// },
|
||||
enhanceApp({ app, router, siteData }) {
|
||||
// ...
|
||||
app.component('SpeakWord', SpeakWord)
|
||||
}
|
||||
} satisfies Theme
|
||||
app.component("SpeakWord", SpeakWord);
|
||||
},
|
||||
} satisfies Theme;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script setup>
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import SpeakWordInlineConverter from '../components/SpeakWordInlineConverter.vue'
|
||||
import ThemedImageSwitch from '../components/ThemedImageSwitch.vue'
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import SpeakWordInlineConverter from "../components/SpeakWordInlineConverter.vue";
|
||||
import ThemedImageSwitch from "../components/ThemedImageSwitch.vue";
|
||||
import { register } from "swiper/element/bundle";
|
||||
|
||||
const { Layout } = DefaultTheme
|
||||
const { Layout } = DefaultTheme;
|
||||
|
||||
register();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -18,12 +20,11 @@ const { Layout } = DefaultTheme
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" >
|
||||
<style lang="scss">
|
||||
@import url(../components/SpeakWord.scss);
|
||||
.speak-word-wrapper {
|
||||
display: inline-block;
|
||||
margin: 0px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -71,12 +71,14 @@
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
|
||||
--vp-font-family-base: 'SF Pro SC','SF Pro Text','SF Pro Icons','PingFang SC','Helvetica Neue', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Helvetica, Arial, sans-serif;
|
||||
--vp-font-family-mono: 'Sauce Code Pro', 'Source Code Pro', 'Courier New', monospace;
|
||||
--vp-font-family-base: "SF Pro SC", "SF Pro Text", "SF Pro Icons",
|
||||
"PingFang SC", "Helvetica Neue", -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, Oxygen, Ubuntu, Helvetica, Arial, sans-serif;
|
||||
--vp-font-family-mono: "Sauce Code Pro", "Source Code Pro", "Courier New",
|
||||
monospace;
|
||||
|
||||
--vp-c-text-strong: rgb(63 86 99);
|
||||
--vp-c-text-em: rgb(91, 4, 17);
|
||||
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -84,7 +86,6 @@
|
||||
--vp-c-text-em: rgb(187, 121, 131);
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -156,15 +157,14 @@ body {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
|
||||
.vp-doc {
|
||||
|
||||
p img, p video {
|
||||
p img,
|
||||
p video {
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
blockquote > p{
|
||||
blockquote > p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -172,12 +172,14 @@ body {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
strong, b {
|
||||
strong,
|
||||
b {
|
||||
color: var(--vp-c-text-strong);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
em, i {
|
||||
em,
|
||||
i {
|
||||
color: var(--vp-c-text-em);
|
||||
}
|
||||
|
||||
@@ -190,7 +192,6 @@ body {
|
||||
margin: auto 1px -4px;
|
||||
}
|
||||
|
||||
|
||||
sup {
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
@@ -199,7 +200,8 @@ body {
|
||||
|
||||
// @TODO 回头撤销对 code 的字体设置,因为 CharisSIL 不是等宽
|
||||
code {
|
||||
font-family: "SauceCodePro Nerd Font Mono", "CharisSIL", "DejaVu Sans Mono", "Courier New", monospace;
|
||||
font-family: "SauceCodePro Nerd Font Mono", "CharisSIL", "DejaVu Sans Mono",
|
||||
"Courier New", monospace;
|
||||
}
|
||||
|
||||
span.pho {
|
||||
@@ -212,7 +214,8 @@ body {
|
||||
// font-size: 100%;
|
||||
color: var(--vp-code-color);
|
||||
&.alt {
|
||||
&::before, &::after {
|
||||
&::before,
|
||||
&::after {
|
||||
content: "/";
|
||||
display: inline-block;
|
||||
width: 0.5em;
|
||||
@@ -227,13 +230,52 @@ audio {
|
||||
margin-top: -0.2em;
|
||||
}
|
||||
|
||||
video, img {
|
||||
video,
|
||||
img {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
span.not-display {display: none;}
|
||||
span.not-display {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.two-column ol, .two-column ul {
|
||||
.two-column ol,
|
||||
.two-column ul {
|
||||
column-count: 2;
|
||||
column-gap: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel {
|
||||
swiper-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
swiper-slide img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.carousel-inner {
|
||||
swiper-slide {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-thumb {
|
||||
padding: 10px 0;
|
||||
|
||||
swiper-slide {
|
||||
width: 25%;
|
||||
height: 100%;
|
||||
opacity: 0.4;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.swiper-slide-thumb-active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
“**1000 小时**” 看起来并不多,也的确不多。但,它有密度要求。每天 1 小时需要大约 3 年才能凑足 1000 小时;而每天 3 小时只需要 1 年就能攒足…… 首先,后者的效果比前者高出不知道多少倍,其次,前者实际上更难做到更难坚持。所以,做不到**每天至少 3 小时**,还不如不做。
|
||||
|
||||
“**就能**” 的意思是说没有例外。“用你的注意力填满 1000 小时” 是 “练成任何你所需要的技能” 的充分必要条件。若是充分必要条件被满足的话,结果就只能如实发生。
|
||||
“**就能**” 的意思是说没有例外。“用你的注意力填满 1000 小时” 是 “练成任何你所需要的技能” 的充分必要条件。若是充分必要条件被满足的话,结果就只能如实发生。
|
||||
|
||||
“**练成**”,不是从书本上 “知道” 或者在哪里 “学到”,不是考试 “及格” 或者 “优秀”,甚至不只是 “做好”,而是 “**精通**” —— 从来少有人做到。
|
||||
|
||||
@@ -70,6 +70,6 @@ AI 凶猛。未来咆哮而至。关于人工智能对人类的威胁,以及
|
||||
|
||||
—— 吾往矣。
|
||||
|
||||
<span style="text-align: right;">**李笑来** *2024* 春节 于 北京</span>
|
||||
<span style="text-align: right;">**李笑来** _2024_ 春节 于 北京</span>
|
||||
|
||||
[^*]: 基尼系数(Gini coefficient),是 20 世纪初意大利学者科拉多·基尼根据洛伦兹曲线所定义的判断年收入分配公平程度的指标,是比例数值,在 0 和 1 之间。基尼系数越小,年收入分配越平均;基尼系数越大,年收入分配越不平均。
|
||||
[^*]: 基尼系数(Gini coefficient),是 20 世纪初意大利学者科拉多·基尼根据洛伦兹曲线所定义的判断年收入分配公平程度的指标,是比例数值,在 0 和 1 之间。基尼系数越小,年收入分配越平均;基尼系数越大,年收入分配越不平均。
|
||||
|
||||
@@ -17,5 +17,9 @@
|
||||
"dev": "vitepress dev",
|
||||
"build": "vitepress build",
|
||||
"preview": "vitepress preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0",
|
||||
"swiper": "^11.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "1000-hours@workspace:1000-hours"
|
||||
dependencies:
|
||||
cheerio: "npm:^1.0.0"
|
||||
markdown-it-footnote: "npm:^4.0.0"
|
||||
markdown-it-ins: "npm:^4.0.0"
|
||||
markdown-it-mark: "npm:^4.0.0"
|
||||
@@ -17,6 +18,7 @@ __metadata:
|
||||
markdown-it-sup: "npm:^2.0.0"
|
||||
mermaid: "npm:^11.2.0"
|
||||
sass: "npm:^1.78.0"
|
||||
swiper: "npm:^11.1.12"
|
||||
vitepress: "npm:^1.3.4"
|
||||
vitepress-plugin-mermaid: "npm:^2.0.16"
|
||||
vue: "npm:^3.5.4"
|
||||
@@ -23060,6 +23062,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"swiper@npm:^11.1.12":
|
||||
version: 11.1.12
|
||||
resolution: "swiper@npm:11.1.12"
|
||||
checksum: 10c0/0d8047dbbf79c35f3515d12c77dc5f82d911add0b7e4ca74a8d351cb572c3a327adc3b10ac10f11571205b8071037537edcb81763808a60e4b6d0a81efebc851
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"symbol-tree@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "symbol-tree@npm:3.2.4"
|
||||
|
||||
Reference in New Issue
Block a user