typescript 博客

Since I'm trying to build a writing habit, well, I'm writing more and more. Even though I use publishing blogs like Medium, dev.to, and Hashnode, I like to post my content on my own blog as well.

由于我正在尝试养成写作习惯,所以我正在写作越来越多。 即使我使用诸如Medium , dev.to和Hashnode之类的发布博客,我也希望将自己的内容发布在自己的博客上 。

As I wanted to build a simple website, this blog is basically HTML and CSS with very little JavaScript. But the thing is, I needed to improve the publishing process.

当我想建立一个简单的网站时,该博客基本上是HTML和CSS,而JavaScript却很少。 但问题是,我需要改进发布过程。

So how does it work now?

那么,现在如何运作?

I manage the blog roadmap on Notion. It looks like this:

我管理有关概念的博客路线图。 看起来像这样:

It's a simple kanban type of board. I like this board because I can get all my ideas into a physical (or digital?) representation. I also use it to build a draft, polish that draft and make it better and better, and then publish it to the blog.

这是一种简单的看板类型的板。 我喜欢这个董事会,因为我可以将所有想法变成物理(或数字?)表示形式。 我还使用它来构建草稿,对该草稿进行完善并使其变得越来越好,然后将其发布到博客中。

So I write my blog post using Notion. After I finish it, I copy the Notion writing and paste it into an online tool to transform markdown into HTML. And then I can use this HTML to create the actual post.

所以我用Notion写我的博客文章。 完成后,我复制了Notion写作并将其粘贴到在线工具中,以将markdown转换为HTML。 然后,我可以使用此HTML来创建实际的帖子。

But this is just the body, the content for the page. I always need to create the whole HTML with the head content, body, and footer.

但这只是正文,即页面的内容。 我总是需要用头部内容,正文和页脚创建整个HTML。

This process is tedious and boring. But good news, it can be automated. And this post is all about this automation. I want to show you the behind the scenes of this new tool I created and what I learned through this process.

这个过程乏味而乏味。 但好消息是,它可以自动化。 而这篇文章就是关于这种自动化的。 我想向您展示我创建的这个新工具的幕后故事,以及我从此过程中学到的知识。

特征 (Features)

My main idea was to have a whole HTML article ready to publish. As I mentioned before, the <head> and <footer> sections don't change much. So I could use those as a "template".

我的主要想法是准备发布整个HTML文章。 如前所述, <head><footer>部分的更改不大。 所以我可以将它们用作“模板”。

With this template, I have the data that can change for each article I write and publish. This data is a variable in the template with this representation {{ variableName }}. An example:

有了这个模板,我拥有的数据可以随着我撰写和发布的每篇文章而改变。 此数据是模板中具有此表示形式{{ variableName }} 。 一个例子:

<h1>{{ title }}</h1>

Now I can use the template and replace the variables with real data – specific info for each article.

现在,我可以使用模板并将变量替换为实际数据,即每篇文章的特定信息。

The second part is the body, the real post. In the template, it is represented by {{ article }}. This variable will be replaced by the HTML generated by Notion markdown.

第二部分是正文,真实帖子。 在模板中,它由{{ article }} 。 此变量将由Notion markdown生成HTML替换。

When we copy and paste notes from Notion, we get kind of a Markdown style. This project will transform this markdown into an HTML and use it as the article variable in the template.

当我们从Notion复制和粘贴笔记时,我们得到了一种Markdown样式。 该项目会将此markdown转换为HTML,并将其用作模板中的article变量。

To create the ideal template, I took a look of all variables I needed to create:

为了创建理想的模板,我查看了创建所需的所有变量:

  • title

    title

  • description

    description

  • date

    date

  • tags

    tags

  • imageAlt

    imageAlt

  • imageCover

    imageCover

  • photographerUrl

    photographerUrl

  • photographerName

    photographerName

  • article

    article

  • keywords

    keywords

With these variables, I created the template.

使用这些变量,我创建了template 。

To pass some of this information to build the HTML, I created a json file as the article config: article.config.json. There I have something like this:

为了传递一些信息来构建HTML,我创建了一个json文件作为config: article.config.json 。 那里我有这样的事情:

{"title": "React Hooks, Context API, and Pokemons","description": "Understanding how hooks and the context api work","date": "2020-04-21","tags": ["javascript","react"],"imageAlt": "The Ash from Pokemon","photographerUrl": "<https://www.instagram.com/kazuh.illust>","photographerName": "kazuh.yasiro","articleFile": "article.md","keywords": "javascript,react"
}

The first step was that the project should know how to open and read the template and the article config. I use this data to populate the template.

第一步是项目应该知道如何打开和阅读模板以及文章配置。 我使用这些数据来填充模板。

Template first:

模板优先:

const templateContent: string = await getTemplateContent();

So we basically need to implement the getTemplateContent function.

因此,我们基本上需要实现getTemplateContent函数。

import fs, { promises } from 'fs';
import { resolve } from 'path';const { readFile } = promises;const getTemplateContent = async (): Promise<string> => {const contentTemplatePath = resolve(__dirname, '../examples/template.html');return await readFile(contentTemplatePath, 'utf8');
};

The resolve with __dirname will get the absolute path to the directory from the source file that is running. And then go to the examples/template.html file. The readFile will asynchronously read and return the content from the template path.

__dirnameresolve将从运行中的源文件获取目录的绝对路径。 然后转到examples/template.html文件。 readFile将异步读取并从模板路径返回内容。

Now we have the template content. And we need to do the same thing for the article config.

现在我们有了模板内容。 我们需要对文章配置执行相同的操作。

const getArticleConfig = async (): Promise<ArticleConfig> => {const articleConfigPath = resolve(__dirname, '../examples/article.config.json');const articleConfigContent = await readFile(articleConfigPath, 'utf8');return JSON.parse(articleConfigContent);
};

Two different things happen here:

这里发生两种不同的事情:

  • As the article.config.json has a json format, we need to transform this json string into a JavaScript object after reading the file

    由于article.config.json具有json格式,因此我们需要在读取文件后将此json字符串转换为JavaScript对象

  • The return of the article config content will be an ArticleConfig as I defined in the function return type. Let's build it.

    如我在函数返回类型中定义的,文章配置内容的返回将是ArticleConfig 。 让我们来构建它。

type ArticleConfig = {title: string;description: string;date: string;tags: string[];imageCover: string;imageAlt: string;photographerUrl: string;photographerName: string;articleFile: string;keywords: string;
};

When we get this content, we also use this new type.

获得此内容后,我们还将使用此新类型。

const articleConfig: ArticleConfig = await getArticleConfig();

Now we can use the replace method to fill the config data in the template content. Just to illustrate the idea, it would look like this:

现在我们可以使用replace方法在模板内容中填充配置数据。 只是为了说明这个想法,它看起来像这样:

templateContent.replace('title', articleConfig.title)

But some variables appear more than one time in the template. Regex to the rescue. With this:

但是某些变量在模板中出现了不止一次。 正则表达式可以解救。 有了这个:

new RegExp('\\{\\{(?:\\\\s+)?(title)(?:\\\\s+)?\\}\\}', 'g');

... I get all the strings that match {{ title }}. So I can build a function that receives a parameter to be found and use it in the title place.

...我得到了所有与{{ title }}匹配的字符串。 因此,我可以构建一个接收要找到的参数的函数,并在标题位置使用它。

const getPattern = (find: string): RegExp =>new RegExp('\\{\\{(?:\\\\s+)?(' + find + ')(?:\\\\s+)?\\}\\}', 'g');

Now we can replace all matches. An example for the title variable:

现在我们可以替换所有匹配项。 title变量的示例:

templateContent.replace(getPattern('title'), articleConfig.title)

But we don't want to replace only the title variable, but all variables from the article config. Replace all!

但是我们不想只替换title变量,而是文章配置中的所有变量。 全部替换!

const buildArticle = (templateContent: string) => ({with: (articleConfig: ArticleAttributes) =>templateContent.replace(getPattern('title'), articleConfig.title).replace(getPattern('description'), articleConfig.description).replace(getPattern('date'), articleConfig.date).replace(getPattern('tags'), articleConfig.articleTags).replace(getPattern('imageCover'), articleConfig.imageCover).replace(getPattern('imageAlt'), articleConfig.imageAlt).replace(getPattern('photographerUrl'), articleConfig.photographerUrl).replace(getPattern('photographerName'), articleConfig.photographerName).replace(getPattern('article'), articleConfig.articleBody).replace(getPattern('keywords'), articleConfig.keywords)
});

Now I replace all! We use it like this:

现在我全部替换! 我们这样使用它:

const article: string = buildArticle(templateContent).with(articleConfig);

But we are missing two parts here:

但是我们在这里缺少两部分:

  • tags

    tags

  • article

    article

In the config json file, the tags is a list. So, for the list:

在config json文件中, tags是一个列表。 因此,对于列表:

['javascript', 'react'];

The final HTML would be:

最终HTML将是:

<a class="tag-link" href="../../../tags/javascript.html">javascript</a>
<a class="tag-link" href="../../../tags/react.html">react</a>

So I created another template: tag_template.html with the {{ tag }} variable. We just need to map the tags list and create each HTML tag template.

因此,我创建了另一个模板: tag_template.html{{ tag }}变量。 我们只需要映射tags列表并创建每个HTML标签模板。

const getArticleTags = async ({ tags }: { tags: string[] }): Promise<string> => {const tagTemplatePath = resolve(__dirname, '../examples/tag_template.html');const tagContent = await readFile(tagTemplatePath, 'utf8');return tags.map(buildTag(tagContent)).join('');
};

Here we:

在这里,我们:

  • get the tag template path获取标签模板路径
  • get the tag template content获取标签模板的内容
  • map through the tags and build the final tag HTML based on the tag template

    映射tags并基于标签模板构建最终标签HTML

The buildTag is a function that returns another function.

buildTag是一个返回另一个函数的函数。

const buildTag = (tagContent: string) => (tag: string): string =>tagContent.replace(getPattern('tag'), tag);

It receives the tagContent - it is the tag template content - and returns a function that receives a tag and builds the final tag HTML. And now we call it to get the article tags.

它接收tagContent (它是标签模板的内容),并返回一个接收标签并构建最终标签HTML的函数。 现在我们称它为获取文章标签。

const articleTags: string = await getArticleTags(articleConfig);

About the article now. It looks like this:

现在关于这篇文章。 看起来像这样:

const getArticleBody = async ({ articleFile }: { articleFile: string }): Promise<string> => {const articleMarkdownPath = resolve(__dirname, `../examples/${articleFile}`);const articleMarkdown = await readFile(articleMarkdownPath, 'utf8');return fromMarkdownToHTML(articleMarkdown);
};

It receives the articleFile, we try to get the path, read the file, and get the markdown content. Then pass this content to fromMarkdownToHTML function to transform the markdown into HTML.

它收到articleFile ,我们尝试获取路径,读取文件,并获取降价内容。 然后将此内容传递给fromMarkdownToHTML函数,以将markdown转换为HTML。

For this part I'm using an external library called showdown. It handles every little corner case to transform markdown into HTML.

对于这一部分,我正在使用一个名为showdown的外部库。 它处理所有小的情况,将markdown转换为HTML。

import showdown from 'showdown';const fromMarkdownToHTML = (articleMarkdown: string): string => {const converter = new showdown.Converter()return converter.makeHtml(articleMarkdown);
};

And now I have the tags and the article HTML:

现在,我有了标签和HTML文章:

const templateContent: string = await getTemplateContent();
const articleConfig: ArticleConfig = await getArticleConfig();
const articleTags: string = await getArticleTags(articleConfig);
const articleBody: string = await getArticleBody(articleConfig);const article: string = buildArticle(templateContent).with({...articleConfig,articleTags,articleBody
});

I missed one more thing! Before, I expected that I always needed to add the image cover path into the article config file. Something like this:

我又错过了一件事! 以前,我希望总是需要将图像封面路径添加到文章配置文件中。 像这样:

{"imageCover": "an-image.png",
}

But we could assume that the image name will be cover. The challenge was the extension. It can be .png, .jpg, .jpeg, or .gif.

但是我们可以假设图像名称为cover 。 挑战是扩展。 它可以是.png.jpg.jpeg.gif

So I built a function to get the right image extension. The idea is to search for the image in the folder. If it exists in the folder, return the extension.

因此,我建立了一个函数来获取正确的图像扩展名。 这个想法是在文件夹中搜索图像。 如果它存在于文件夹中,则返回扩展名。

I started with the "existing" part.

我从“现有”部分开始。

fs.existsSync(`${folder}/${fileName}.${extension}`);

Here I'm using the existsSync function to find the file. If it exists in the folder, it returns true. Otherwise, false.

在这里,我使用existsSync函数来查找文件。 如果它存在于文件夹中,则返回true。 否则为假。

I added this code into a function:

我将此代码添加到一个函数中:

const existsFile = (folder: string, fileName: string) => (extension: string): boolean =>fs.existsSync(`${folder}/${fileName}.${extension}`);

Why did I do it this way?

我为什么要这样做?

Using this function, I need to pass the folder, the filename, and the extension. The folder and the filename are always the same. The difference is the extension.

使用此功能,我需要传递folderfilenameextensionfolderfilename始终相同。 区别在于extension

So I could build a function using curry. That way, I can build different functions for the same folder and filename. Like this:

因此,我可以使用curry构建函数。 这样,我可以为同一folderfilename构建不同的功能。 像这样:

const hasFileWithExtension = existsFile(examplesFolder, imageName);hasFileWithExtension('jpeg'); // true or false
hasFileWithExtension('jpg'); // true or false
hasFileWithExtension('png'); // true or false
hasFileWithExtension('gif'); // true or false

The whole function would look like this:

整个功能如下所示:

const getImageExtension = (): string => {const examplesFolder: string = resolve(__dirname, `../examples`);const imageName: string = 'cover';const hasFileWithExtension = existsFile(examplesFolder, imageName);if (hasFileWithExtension('jpeg')) {return 'jpeg';}if (hasFileWithExtension('jpg')) {return 'jpg';}if (hasFileWithExtension('png')) {return 'png';}return 'gif';
};

But I didn't like this hardcoded string to represent the image extension. enum is really cool!

但是我不喜欢这个硬编码的字符串来表示图像扩展名。 enum真的很棒!

enum ImageExtension {JPEG = 'jpeg',JPG = 'jpg',PNG = 'png',GIF = 'gif'
};

And the function now using our new enum ImageExtension:

现在使用我们新的枚举ImageExtension的函数:

const getImageExtension = (): string => {const examplesFolder: string = resolve(__dirname, `../examples`);const imageName: string = 'cover';const hasFileWithExtension = existsFile(examplesFolder, imageName);if (hasFileWithExtension(ImageExtension.JPEG)) {return ImageExtension.JPEG;}if (hasFileWithExtension(ImageExtension.JPG)) {return ImageExtension.JPG;}if (hasFileWithExtension(ImageExtension.PNG)) {return ImageExtension.PNG;}return ImageExtension.GIF;
};

Now I have all the data to fill the template. Great!

现在,我拥有所有数据以填充模板。 大!

As the HTML is done, I want to create the real HTML file with this data. I basically need to get the correct path, the HTML, and use the writeFile function to create this file.

HTML完成后,我想使用此数据创建实际HTML文件。 我基本上需要获取正确的路径,HTML,并使用writeFile函数创建此文件。

To get the path, I needed to understand the pattern of my blog. It organizes the folder with the year, the month, the title, and the file is named index.html.

要获取路径,我需要了解我的博客的模式。 它用年,月,标题组织文件夹,文件名为index.html

An example would be:

一个例子是:

2020/04/publisher-a-tooling-to-blog-post-publishing/index.html

At first, I thought about adding this data to the article config file. So every time I need to update this attribute from the article config to get the correct path.

最初,我考虑过将这些数据添加到文章配置文件中。 因此,每次我需要从文章配置中更新此属性以获取正确的路径时。

But another interesting idea was to infer the path by some data we already have in the article config file. We have the date (e.g. "2020-04-21") and the title (e.g. "Publisher: tooling to automate blog post publishing").

但是另一个有趣的想法是根据文章配置文件中已有的一些数据来推断路径。 我们有date (例如"2020-04-21" )和title (例如"Publisher: tooling to automate blog post publishing" )。

From the date, I can get the year and the month. From the title, I can generate the article folder. The index.html file is always constant.

从日期起,我可以得到年和月。 从标题中,我可以生成文章文件夹。 index.html文件始终是恒定的。

The string would look like this:

该字符串如下所示:

`${year}/${month}/${slugifiedTitle}`

For the date, it is really simple. I can split by - and destructure:

对于日期,这真的很简单。 我可以按-进行分解:

const [year, month]: string[] = date.split('-');

For the slugifiedTitle, I built a function:

对于slugifiedTitle ,我构建了一个函数:

const slugify = (title: string): string =>title.trim().toLowerCase().replace(/[^\\w\\s]/gi, '').replace(/[\\s]/g, '-');

It removes the white spaces from the beginning and the end of the string. Then downcase the string. Then remove all special characters (keep only word and whitespace characters). And finally, replace all whitespaces with a -.

它从字符串的开头和结尾删除空格。 然后将字符串小写。 然后删除所有特殊字符(仅保留单词和空格字符)。 最后,用-替换所有空格。

The whole function looks like this:

整个功能如下所示:

const buildNewArticleFolderPath = ({ title, date }: { title: string, date: string }): string => {const [year, month]: string[] = date.split('-');const slugifiedTitle: string = slugify(title);return resolve(__dirname, `../../${year}/${month}/${slugifiedTitle}`);
};

This function tries to get the article folder. It doesn't generate the new file. This is why I didn't add the /index.html to the end of the final string.

此功能尝试获取文章文件夹。 它不会生成新文件。 这就是为什么我没有将/index.html添加到最终字符串的末尾。

Why did it do that? Because, before writing the new file, we always need to create the folder. I used mkdir with this folder path to create it.

为什么这样做呢? 因为在写入新文件之前,我们始终需要创建文件夹。 我将mkdir与该文件夹路径一起使用来创建它。

const newArticleFolderPath: string = buildNewArticleFolderPath(articleConfig);
await mkdir(newArticleFolderPath, { recursive: true });

And now I could use the folder the create the new article file in it.

现在,我可以使用文件夹在其中创建新的文章文件。

const newArticlePath: string = `${newArticleFolderPath}/index.html`;
await writeFile(newArticlePath, article);

One thing we are missing here: as I added the image cover in the article config folder, I needed to copy it and paste it into the right place.

我们在这里缺少的一件事:当我将图像封面添加到article config文件夹中时,我需要将其复制并粘贴到正确的位置。

For the 2020/04/publisher-a-tooling-to-blog-post-publishing/index.html example, the image cover would be in the assets folder:

对于2020/04/publisher-a-tooling-to-blog-post-publishing/index.html示例,图像封面应位于资产文件夹中:

2020/04/publisher-a-tooling-to-blog-post-publishing/assets/cover.png

To do this, I need two things:

为此,我需要做两件事:

  • create a new assets folder with mkdir

    使用mkdir创建一个新的assets文件夹

  • copy the image file and paste it into the new folder with copyFile

    复制图像文件并将其粘贴到带有copyFile的新文件夹中

To create the new folder, I just need the folder path. To copy and paste the image file, I need the current image path and the article image path.

要创建新文件夹,我只需要文件夹路径。 要复制和粘贴图像文件,我需要当前图像路径和文章图像路径。

For the folder, as I have the newArticleFolderPath, I just need to concatenate this path to the assets folder.

对于文件夹,因为有了newArticleFolderPath ,我只需要将此路径连接到资产文件夹。

const assetsFolder: string = `${newArticleFolderPath}/assets`;

For the current image path, I have the imageCoverFileName with the correct extension. I just need to get the image cover path:

对于当前图像路径,我具有带有正确扩展名的imageCoverFileName 。 我只需要获取图像封面路径:

const imageCoverExamplePath: string = resolve(__dirname, `../examples/${imageCoverFileName}`);

To get the future image path, I need to concatenate the image cover path and the image file name:

为了获得将来的图像路径,我需要将图像封面路径和图像文件名连接起来:

const imageCoverPath: string = `${assetsFolder}/${imageCoverFileName}`;

With all these data, I can create the new folder:

使用所有这些数据,我可以创建新文件夹:

await mkdir(assetsFolder, { recursive: true });

And copy and paste the image cover file:

并复制并粘贴图像封面文件:

await copyFile(imageCoverExamplePath, imageCoverPath);

As I was implementing this paths part, I saw I could group them all into a function buildPaths.

在实现此paths部分时,我看到可以将它们全部分组到一个函数buildPaths

const buildPaths = (newArticleFolderPath: string): ArticlePaths => {const imageExtension: string = getImageExtension();const imageCoverFileName: string = `cover.${imageExtension}`;const newArticlePath: string = `${newArticleFolderPath}/index.html`;const imageCoverExamplePath: string = resolve(__dirname, `../examples/${imageCoverFileName}`);const assetsFolder: string = `${newArticleFolderPath}/assets`;const imageCoverPath: string = `${assetsFolder}/${imageCoverFileName}`;return {newArticlePath,imageCoverExamplePath,imageCoverPath,assetsFolder,imageCoverFileName};
};

I also created the ArticlePaths type:

我还创建了ArticlePaths类型:

type ArticlePaths = {newArticlePath: string;imageCoverExamplePath: string;imageCoverPath: string;assetsFolder: string;imageCoverFileName: string;
};

And I could use the function to get all the path data I needed:

而且我可以使用该函数来获取所需的所有路径数据:

const {newArticlePath,imageCoverExamplePath,imageCoverPath,assetsFolder,imageCoverFileName
}: ArticlePaths = buildPaths(newArticleFolderPath);

The last part of the algorithm now! I wanted to quickly validate the created post. So what if I could open the created post in a browser tab? That would be amazing!

现在算法的最后一部分! 我想快速验证创建的帖子。 那么,如果可以在浏览器选项卡中打开创建的帖子怎么办? 这将是惊人的!

So I did it:

所以我做到了:

await open(newArticlePath);

Here I'm using the open library to simulate the terminal open command.

在这里,我使用open库来模拟终端打开命令。

And that was it!

就是这样!

我学到的是 (What I learned)

This project was a lot of fun! I learned some cool things through this process. I want to list them here:

这个项目很有趣! 通过这个过程,我学到了一些很酷的东西。 我想在这里列出它们:

  • As I'm learning Typescript, I wanted to quickly validate the code I was writing. So I configured nodemon to compile and run the code on every file save. It is cool to make the development process so dynamic.

    在学习Typescript时 ,我想快速验证我正在编写的代码。 因此,我将nodemon配置为在每次保存文件时编译并运行代码。 使开发过程如此动态是很酷的。

  • I tried to use the new node fs's promises: readFile, mkdir, writeFile, and copyFile. It is on Stability: 2.

    我尝试使用新节点fspromisesreadFilemkdirwriteFilecopyFile 。 它的Stability: 2

  • I did a lot of currying for some functions to make them reusable.

    我做了很多讨好某些功能,使其可重复使用。

  • Enums and Types are good ways to make the state consistent in Typescript, but also make a good representation and documentation of all the project's data. Data contracts are a really nice thing.

    枚举和类型是使状态在Typescript中保持一致的好方法,但也可以很好地表示和记录所有项目数据。 数据合同确实是一件好事。

  • The tooling mindset. This is one of the things I really love about programming. Build toolings to automate repetitive tasks and make life easier.工具思维方式。 这是我真正喜欢编程的一件事。 构建工具以自动执行重复性任务并简化工作。

I hope it was good reading! Keep learning and coding!

我希望这是一本好书! 继续学习和编码!

This post was originally published on my blog.

该帖子最初发布在我的博客上 。

My Twitter and Github.

我的Twitter和Github 。

资源资源 (Resources)

  • Publisher Tooling: source code

    Publisher Tooling:源代码

  • Thinking in data contracts

    数据合同中的思考

  • Typescript Learnings

    打字稿学习

  • Closures, Currying, and Cool Abstractions

    闭包,咖喱和酷抽象

  • Learn React by building an App

    通过构建应用来学习React

翻译自: https://www.freecodecamp.org/news/automating-my-blog-posts-publishing-process-with-typescript/

typescript 博客

typescript 博客_如何使用Typescript自动化博客发布过程相关推荐

  1. ps 入门 传智播客_如果您喜欢写博客,则应尝试播客。 这是入门方法。

    ps 入门 传智播客 by Jeff Meyerson 杰夫·梅耶森(Jeff Meyerson) 如果您喜欢写博客,则应尝试播客. 这是入门方法. (If you like blogging, yo ...

  2. 怎么搭建自己的播客_如何开始自己的播客(逐步)

    怎么搭建自己的播客 Are you looking to start a podcast? Want to know what does a perfect podcasting setup look ...

  3. typescript中函数_如何在TypeScript中合成Canvas动画

    typescript中函数 by Changhui Xu 徐昌辉 如何在TypeScript中合成Canvas动画 (How to Compose Canvas Animations in TypeS ...

  4. html编写个人博客_第 06 篇:博客从“裸奔”到“有皮肤”

    作者:HelloGitHub-追梦人物 文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 点击本文最下方的"阅读原文"即可获取 在此之前我们已经编写了博客的 ...

  5. armbian nginx 部署博客_通过Git将Hexo博客部署到服务器

    本文首发于我的个人博客https://orxing.top,欢迎来访 服务器是用的阿里云ECS CentOS,本来是用来部署WordPress的,后来接触了Hexo,就把Hexo直接部署到了GitHu ...

  6. typora绑定github博客_零基础搭建个人博客

    因为懒得再写一遍了,所以直接复制过来了,建议直接去文章地址查看 文章链接: https://hmoumou.xyz/2020/08/14/shi-yong-github-pages-hexo-da-j ...

  7. 怎么禁止/开启Ubuntu自动更新升级_豆豆技术派的博客-CSDN博客_ubuntu 自动更新

    怎么禁止/开启Ubuntu自动更新升级_豆豆技术派的博客-CSDN博客_ubuntu 自动更新

  8. ubuntu下解决“无法获得锁 ”的方法_cppmylove的博客-CSDN博客_无法获得锁

    ubuntu下解决"无法获得锁 "的方法_cppmylove的博客-CSDN博客_无法获得锁 E: 无法获得锁 /var/lib/dpkg/lock-frontend - open ...

  9. 无人驾驶运动学模型——线性时变模型预测控制的思路推演过程_百叶书的博客-CSDN博客_线性时变模型预测控制 转

    无人驾驶运动学模型--线性时变模型预测控制的思路推演过程_百叶书的博客-CSDN博客_线性时变模型预测控制

最新文章

  1. 主成分分析(PCA)Python代码实现
  2. 正则表达式最常用的符号匹配
  3. CentOS6静态网络配置
  4. C# Revert 单词反转字符串!『测试通过』
  5. AWS — AWS CloudFormation
  6. 2020 年终总结:变化 积累
  7. 人生中一定要坚守的格言
  8. 游戏脚本在移动游戏设计中的作用_游戏设计中道具设计分步解说
  9. linux系统的文件系统tmpfs,linux里tmpfs文件系统
  10. ajax 用户验证js,js ajax验证用户名
  11. Java正则表达式, 提取双引号中间的部分
  12. 实习日志(1)2011-12-30
  13. Eigen删除矩阵的某行或某列
  14. devc 能优化吗_小网站能做seo优化吗?如何为小公司网站做seo优化?
  15. 如何进行Monkey Test
  16. windows下查找mysql配置文件my.ini
  17. 一段java代码_写了一段Java代码,希望大家多多指点
  18. 商务与经济统计 笔记
  19. 新款戴尔取消开盖自动开机办法,以戴尔7591为例子如下
  20. 2022头像小程序源码+支持姓氏头像生成

热门文章

  1. ubuntu 修改 ssh默认端口号
  2. 演练 课程导航 1002 html
  3. 从数据库层面手动删除zabbix告警
  4. Glide-源码分析(三)
  5. matlab-线性代数 判断 det 矩阵是否可逆
  6. 基于Node.js实现压缩和解压缩的方法
  7. 什么是 Unix 以及它为什么这么重要?
  8. Maven添加本地依赖
  9. python 之Requests库学习笔记
  10. VIEW SERVER STATE permission was denied on object 'server', database 'master'