likes
comments
collection
share

使用vite-ssg写一个存放笔记的小网站

作者站长头像
站长
· 阅读数 38

代码仓库

我的:github.com/lrsoy/me

antfu:github.com/antfu/antfu…

参考

写这个东西参考的是 antfu 的博客Anthony Fu (antfu.me) 这是他的博客地址,比较简约,其实我参考他的这个一部分是参考他的代码(因为刚开始弄这个找了很多太麻烦了,直接找仓库按照他的代码走一遍),另一部分就是布局上的一些内容,在编写的过程中我还参考了其他人的博客:虹墨空间站 www.imaegoo.com/ 这是的他的博客,我参考他的部分是他的背景,我觉得还挺好看的,就从他那里要到了这个背景源码 github.com/fan-lv/Fan/… 。总之参考的内容都在这里了,我还有一部分的内容还没有完成,但是大部分我已经写完了。

我写这个我不会把全部的流程写一遍,因为有些内容完全可以直接参考antfu的代码或者是我写的直接走一遍就行了,我只将里面我认为比较引起大家注意的部分拿出来,例如代码块显示行号,以及目录传送到任何位置的问题。

效果

首页 使用vite-ssg写一个存放笔记的小网站

分类文章列表

使用vite-ssg写一个存放笔记的小网站

点击列表查看文章内容

使用vite-ssg写一个存放笔记的小网站

推荐使用的图标库

icones.js.org/ 这个图标库很不错,而且他和unocss 还是配套的,而且它们作者是antfu,这个图标库里面包含了很多的图标,使用起来也是很方便,还可以下载svg等等,也可以下载vue或者是react的代码,可以通过css类的方式进行使用,非常好用。

背景

这个背景原链接github.com/fan-lv/Fan/… ,源代码是通过js写的,后来我就改改,改成了ts版本的,如果要是创建不带ts的项目的话,可以直接拿这个代码,下面是ts版本的

<!-- Plum -->

<script setup lang="ts">
const el = ref<HTMLCanvasElement | null>(null)
const size = reactive(useWindowSize())
const starCount = ref<number>(0)
const starDensity = ref<number>(0.216)
const speedCoeff = ref<number>(0.05)
interface Color {
  giantColor: string;
  starColor: string;
  cometColor: string;
}
const first = ref<boolean>(true)
const color = reactive<Color>({
  giantColor: '180,184,240',
  starColor: '226,225,142',
  cometColor: '226,225,224'
});

interface Star {
  giant: boolean;
  comet: boolean;
  x: number;
  y: number;
  r: number;
  dx: number;
  dy: number;
  fadingOut: boolean | null;
  fadingIn: boolean;
  opacity: number;
  opacityTresh: number;
  do: number;
  reset(): void;
  fadeIn(): void;
  fadeOut(): void;
  draw(): void;
  move(): void;
}
const grain = ref<Array<Star>>()

function initCanvas(canvas: HTMLCanvasElement, width = 400, height = 400, _dpi?: number) {
  const ctx = canvas.getContext('2d')!

  const dpr = window.devicePixelRatio || 1
  // @ts-expect-error vendor
  const bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1

  const dpi = _dpi || dpr / bsr

  canvas.style.width = `${width}px`
  canvas.style.height = `${height}px`
  canvas.width = dpi * width
  canvas.height = dpi * height
  ctx.scale(dpi, dpi)

  starCount.value = width * starDensity.value

  return { ctx, dpi }
}

function getProbability(percents: number) {
  return ((Math.floor(Math.random() * 1000) + 1) < percents * 10);
}

function getRandInterval(min: number, max: number) {
  return (Math.random() * (max - min) + min);
}
onMounted(async () => {
  const canvas = el.value!
  const { ctx } = initCanvas(canvas, size.width, size.height)
  const { width, height } = canvas

  function Star(): Star {
    const star = {} as Star;
    star.reset = function () {
      star.giant = getProbability(3);
      star.comet = star.giant || first.value ? false : getProbability(10);
      star.x = getRandInterval(0, width - 10);
      star.y = getRandInterval(0, height);
      star.r = getRandInterval(1.1, 2.6);
      star.dx =
        getRandInterval(speedCoeff.value, 6 * speedCoeff.value) +
        (star.comet as any as number + 1 - 1) * speedCoeff.value * getRandInterval(50, 120) +
        speedCoeff.value * 2;
      star.dy =
        -getRandInterval(speedCoeff.value, 6 * speedCoeff.value) -
        (star.comet as any as number + 1 - 1) * speedCoeff.value * getRandInterval(50, 120);
      star.fadingOut = null;
      star.fadingIn = true;
      star.opacity = 0;
      star.opacityTresh = getRandInterval(0.2, 1 - (star.comet as any as number + 1 - 1) * 0.4);
      star.do = getRandInterval(0.0005, 0.002) + (star.comet as any as number + 1 - 1) * 0.001;
    };

    star.fadeIn = function () {
      if (star.fadingIn) {
        star.fadingIn = !(star.opacity > star.opacityTresh);
        star.opacity += star.do;
      }
    };

    star.fadeOut = function () {
      if (star.fadingOut) {
        star.fadingOut = !(star.opacity < 0);
        star.opacity -= star.do / 2;
        if (star.x > width || star.y < 0) {
          star.fadingOut = false;
          star.reset();
        }
      }
    };

    star.draw = function () {
      ctx.beginPath();
      if (star.giant) {
        ctx.fillStyle = `rgba(${color.giantColor},${star.opacity})`;
        ctx.arc(star.x, star.y, 2, 0, 2 * Math.PI, false);
      } else if (star.comet) {
        ctx.fillStyle = `rgba(${color.cometColor},${star.opacity})`;
        ctx.arc(star.x, star.y, 1.5, 0, 2 * Math.PI, false);
        for (let i = 0; i < 30; i++) {
          ctx.fillStyle = `rgba(${color.cometColor},${star.opacity - (star.opacity / 20) * i})`;
          ctx.rect(
            star.x - (star.dx / 4) * i,
            star.y - (star.dy / 4) * i - 2,
            2,
            2
          );
          ctx.fill();
        }
      } else {
        ctx.fillStyle = 'rgba(' + color.starColor + ',' + this.opacity + ')';
        ctx.rect(star.x, star.y, star.r, star.r);
      }
      ctx.closePath();
      ctx.fill();
    }
    star.move = function () {
      star.x += star.dx;
      star.y += star.dy;
      if (star.fadingOut === false) {
        star.reset();
      }
      if (star.x > width - (width / 4) || star.y < 0) {
        star.fadingOut = true;
      }
    };
    setTimeout(function () {
      first.value = false;
    }, 50);
    return star
  }
  const draw = () => {
    ctx.clearRect(0, 0, width, height);
    grain.value?.forEach(f => {
      f.move();
      f.fadeIn();
      f.fadeOut();
      f.draw();
    })
    window.requestAnimationFrame(draw);
  }
  const createUniverse = () => {
    grain.value = Array.from({ length: starCount.value }, () => {
      const m = Star();
      m.reset();
      return m;
    });
    draw();
  }
  createUniverse()

  useEventListener(window, 'resize', () => initCanvas(canvas, size.width, size.height))
})


</script>
<template>
  <div class="fixed top-0 bottom-0 left-0 right-0 pointer-events-none">
    <canvas id="universe" ref="el"></canvas>
  </div>
</template>

<style ></style>


其中这里面我也用到了useEventListener,他来自 @vueuse/core 库,相信大家应该也知道这个。

markdown-it-table-of-contents生成目录后放到指定位置

在编写这个代码的时候,一般的你像稀土掘金,语雀等等这些可以写文章发笔记的,文章都会显示目录,当使用这个markdown-it-table-of-contents生成目录的时候,他是直接和文章一起生成的html,虽然可以通过css去定位他的位置,为什么我要去让它可以放到任何位置,是因为我这个文章区域我使用了动画,动画影响到了我的定位等等一些其他问题,我就想把他放到我想要放的位置,然后在去做一些css上的处理

实现我使用的vue中的一个组件传送门Teleport,可以将这个插件生成的目录传送到你想要传送的位置,但是在传送的过程中,我发现他并不能传送到我想要传送的位置

一开始我是将Teleport直接在md文档中直接使用,因为它们的一些插件是可以解析md中的vue代码的,所以我就直接在md中使用了这个组件,其中 [[toc]] 是markdown-it-table-of-contents需要的标记

<Teleport>

[[toc]]
</Teleport>

但是如果直接使用了这个组件就会产生一个问题就是,我的布局是在vue代码中写的,当我跳转到这个文章的时候,我的组件里面的dom元素还没创建完,就导致我现在传送的时候不成功,并且控制台会警告说我传送的目标元素不存在,下面是我的解决办法,目前来看是成功的,一直没有出问题,也可以直接传送到我想要的位置。

<!-- DelayTeleport -->
<script setup lang="ts">
import { Teleport } from 'vue'
const slots = useSlots()
const teleportVnode = ref<VNode | null>(null)
onMounted(() => {
  if (slots.default?.()) {
    teleportVnode.value = h(Teleport,
      {
        to: '#view_side'
      },
      slots.default?.()
    )
  }
})

const delayTeleport = {
  render() {
    return h('div', null, [teleportVnode.value])
  }
}
</script>
<template>
  <delayTeleport>
    <slot></slot>
  </delayTeleport>
</template>

<style  scoped></style>

我直接将它写成一个组件并且里面通过插槽以及h函数重新编写,然后在md中使用的时候,将传送门组件直接换成这个组件即可,其中 #view_side是我要传送的位置

代码块相关的处理

原来使用他自己的这些就行,但是我想要给代码添加行号就很费劲,尝试了很多方式,最后我选择使用vuepress实现的代码行号,我将他的实现拿了出来,然后处理成ts版本,发现很好用,而且这里面也包含了vuepress里面的代码块某一行代码需要高亮的功能,下面是完整的代码

resolveVPre.ts

/**
 * Resolve the `:v-pre` / `:no-v-pre` mark from token info
 */
export const resolveVPre = (info: string): boolean | null => {
  if (/:v-pre\b/.test(info)) {
    return true;
  }
  if (/:no-v-pre\b/.test(info)) {
    return false;
  }
  return null;
};

resolveLineNumbers.ts

/**
 * Resolve the `:line-numbers` / `:no-line-numbers` mark from token info
 */
export const resolveLineNumbers = (info: string): boolean | null => {
  if (/:line-numbers\b/.test(info)) {
    return true;
  }
  if (/:no-line-numbers\b/.test(info)) {
    return false;
  }
  return null;
};

resolveLanguage.ts

import * as languages from './languages';


interface resolveLanguageLanguage {
  name: string;
  ext: string;
  aliases: string[];
}
/**
 * A key-value map to get language info from alias
 *
 * - key: alias
 * - value: language
 */
let languagesMap: { [alias: string]: resolveLanguageLanguage };

/**
 * Lazy generate languages map
 */
const getLanguagesMap = (): { [alias: string]: resolveLanguageLanguage } => {
  if (!languagesMap) {
    languagesMap = Object.values(languages).reduce((result, item) => {
      item.aliases.forEach((alias) => {
        result[alias] = item;
      });
      return result;
    }, {} as { [alias: string]: resolveLanguageLanguage });
  }
  return languagesMap;
};

/**
 * Resolve language for highlight from token info
 */
export const resolveLanguage = (info: string): resolveLanguageLanguage => {
  // get user-defined language alias
  const alias = info.match(/^([^ :[{]+)/)?.[1] || 'text';

  // if the alias does not have a match in the map
  // fallback to the alias itself
  return (
    getLanguagesMap()[alias] ?? {
      name: alias,
      ext: alias,
      aliases: [alias],
    }
  );
};

resolveHighlightLines.ts

/**
 * Resolve highlight-lines ranges from token info
 */
export function resolveHighlightLines(info: string): number[][] | null {
  // try to match highlight-lines mark
  const match = info.match(/{([\d,-]+)}/);
  // no highlight-lines mark, return `null`
  if (match === null) {
    return null;
  }
  // resolve lines ranges from the highlight-lines mark
  return match[1].split(",").map((item) => {
    const range = item.split("-");
    if (range.length === 1) {
      range.push(range[0]);
    }
    return range.map((str) => Number.parseInt(str, 10));
  });
}

/**
 * Check if a line number is in ranges
 */
export function isHighlightLine(lineNumber: number, ranges: number[][]): boolean {
  return ranges.some(([start, end]) => lineNumber >= start && lineNumber <= end);
}

languages.ts

interface Language {
  name: string;
  ext: string;
  aliases: string[];
}

const languageBash: Language = {
  name: 'bash',
  ext: 'sh',
  aliases: ['bash', 'sh', 'shell', 'zsh'],
};

const languageCsharp: Language = {
  name: 'csharp',
  ext: 'cs',
  aliases: ['cs', 'csharp'],
};

const languageDocker: Language = {
  name: 'docker',
  ext: 'docker',
  aliases: ['docker', 'dockerfile'],
};

const languageFsharp: Language = {
  name: 'fsharp',
  ext: 'fs',
  aliases: ['fs', 'fsharp'],
};

const languageJavascript: Language = {
  name: 'javascript',
  ext: 'js',
  aliases: ['javascript', 'js'],
};

const languageKotlin: Language = {
  name: 'kotlin',
  ext: 'kt',
  aliases: ['kotlin', 'kt'],
};

const languageMarkdown: Language = {
  name: 'markdown',
  ext: 'md',
  aliases: ['markdown', 'md'],
};

const languagePython: Language = {
  name: 'python',
  ext: 'py',
  aliases: ['py', 'python'],
};

const languageRuby: Language = {
  name: 'ruby',
  ext: 'rb',
  aliases: ['rb', 'ruby'],
};

const languageRust: Language = {
  name: 'rust',
  ext: 'rs',
  aliases: ['rs', 'rust'],
};

const languageStylus: Language = {
  name: 'stylus',
  ext: 'styl',
  aliases: ['styl', 'stylus'],
};

const languageTypescript: Language = {
  name: 'typescript',
  ext: 'ts',
  aliases: ['ts', 'typescript'],
};

const languageYaml: Language = {
  name: 'yaml',
  ext: 'yml',
  aliases: ['yaml', 'yml'],
};

export {
  languageBash,
  languageCsharp,
  languageDocker,
  languageFsharp,
  languageJavascript,
  languageKotlin,
  languageMarkdown,
  languagePython,
  languageRuby,
  languageRust,
  languageStylus,
  languageTypescript,
  languageYaml,
};

codePlugin.ts

import { resolveHighlightLines, isHighlightLine } from "./resolveHighlightLines";
import { resolveLanguage } from './resolveLanguage';
import { resolveLineNumbers } from './resolveLineNumbers'
import { resolveVPre } from "./resolveVPre";
import type { PluginWithOptions } from 'markdown-it';
export interface CodePluginOptions {
  highlightLines?: boolean;
  lineNumbers?: boolean | number;
  preWrapper?: boolean;
  vPre?: {
    block?: boolean;
    inline?: boolean;
  };
}

/**
 * Code plugin
 */
const codePlugin: PluginWithOptions<CodePluginOptions> = (md, { highlightLines = true, lineNumbers = true, preWrapper = true, vPre: { block: vPreBlock = true, inline: vPreInline = true } = {}, } = {}) => {
  // override default fence renderer
  md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
    const token = tokens[idx];
    // get token info
    const info = token.info ? md.utils.unescapeAll(token.info).trim() : '';
    // resolve language from token info
    const language = resolveLanguage(info);
    const languageClass = `${options.langPrefix}${language.name}`;
    // try to get highlighted code
    const code = options.highlight?.(token.content, language.name, '') ||
      md.utils.escapeHtml(token.content);
    // wrap highlighted code with `<pre>` and `<code>`
    let result = code.startsWith('<pre')
      ? code
      : `<pre class="${languageClass}"><code>${code}</code></pre>`;
    // resolve v-pre mark from token info
    const useVPre = resolveVPre(info) ?? vPreBlock;
    if (useVPre) {
      result = `<pre v-pre${result.slice('<pre'.length)}`;
    }
    // if `preWrapper` is disabled, return directly
    if (!preWrapper) {
      return result;
    }
    // code fences always have an ending `\n`, so we should trim the last line
    const lines = code.split('\n').slice(0, -1);
    // resolve highlight line ranges from token info
    const highlightLinesRanges = highlightLines
      ? resolveHighlightLines(info)
      : null;
    // generate highlight lines
    if (highlightLinesRanges) {
      const highlightLinesCode = lines
        .map((_, index) => {
          if (isHighlightLine(index + 1, highlightLinesRanges)) {
            return '<div class="highlight-line">&nbsp;</div>';
          }
          return '<br>';
        })
        .join('');
      result = `${result}<div class="highlight-lines">${highlightLinesCode}</div>`;
    }
    // resolve line-numbers mark from token info
    const useLineNumbers = resolveLineNumbers(info) ??
      (typeof lineNumbers === 'number'
        ? lines.length >= lineNumbers
        : lineNumbers);
    // generate line numbers
    if (useLineNumbers) {
      // generate line numbers code
      const lineNumbersCode = lines
        .map(() => `<div class="line-number"></div>`)
        .join('');
      result = `${result}<div class="line-numbers" aria-hidden="true">${lineNumbersCode}</div>`;
    }
    result = `<div class="${languageClass} ext-${language.ext}${useLineNumbers ? ' line-numbers-mode' : ''}">${result}</div>`;
    return result;
  };

  if (vPreInline) {
    const rawInlineCodeRule = md.renderer.rules.code_inline;
    if (rawInlineCodeRule) {
      md.renderer.rules.code_inline = (tokens, idx, options, env, slf) => {
        const token = tokens[idx];
        const lang = token.info ? md.utils.escapeHtml(token.info).trim() : '';
        const code = md.utils.escapeHtml(token.content);
        return `<code class="${options.langPrefix}${lang}">${code}</code>`;
      };
      // md.renderer.rules.code_inline = (tokens, idx, options, env, slf) => {
      //   const result = rawInlineCodeRule(tokens, idx, options, env, slf);
      //   return `<code v-pre${result.slice('<code'.length)}`;
      // };
    }
  }
};

export default codePlugin

其中还有好多css太多了,我就不复制了,直接在我的github仓库里面直接找吧,太长了。其他的内容等我写完我就全部补充上,现在就这几块我认为比较重要的部分吧。

转载自:https://juejin.cn/post/7248914499913908285
评论
请登录