独书先生 Menu

我做了一个 Markdown 编辑器,支持图文卡片、公众号排版和一键发布 WordPress

前段时间,我一直在做一个叫 MarkPress 的工具。 最初,我只是想找一个顺手的地方写 Markdown。做着做着才发现,真正让我觉得麻烦的并不是写作,而是文章写完之后的那一串工作。

前段时间,我一直在做一个叫 MarkPress 的工具。

最初,我只是想找一个顺手的地方写 Markdown。做着做着才发现,真正让我觉得麻烦的并不是写作,而是文章写完之后的那一串工作。

写完要预览,图片要上传图床;发微信公众号要重新排版;发 WordPress 要登录后台、复制粘贴,再检查一遍格式;想从文章里拆几张社交媒体图片,又得换到另一个工具重新整理。

内容明明已经写好了,后面却还有很多重复劳动。

MarkPress 就是从这里开始的。现在,我把它叫作一个本地优先的 Markdown 写作、预览与发布工作台。

打开就能写

MarkPress 不注册也可以直接使用。

它支持 Markdown 实时预览、GFM 和代码高亮。编辑器没有堆太多按钮,因为我自己写东西时,更希望工具安静一点,不要一直提醒我还有多少功能可以点。

文档默认保存在浏览器本地。只要还在当前设备上,即使断网也可以继续写,内容不会因为打开编辑器就自动上传到服务器。

需要在不同设备之间写作时,可以登录并开启云同步。只有使用云同步或部分需要服务器处理的功能时,相应内容才会上传。

你也可以随时把原稿下载成普通的 Markdown 文件。以后即使不再使用 MarkPress,这些文档仍然可以在其他编辑器里继续打开和修改。

一份原稿,完成不同平台的发布

我平时会把同一篇内容发到博客、微信公众号和社交平台。

以前每增加一个发布渠道,几乎就要多维护一个版本:公众号有一份,WordPress 有一份,做成图片以后又是另一份。内容稍微修改一下,几个版本还得跟着一起改。

现在我的做法是只维护 Markdown 原稿。

文章先在一个地方写完,准备发布时,再由 MarkPress 把它转换成不同平台需要的格式。

图片与图床

写博客时,图片通常是绕不开的一步。

MarkPress 支持连接 S.EE、Cloudinary 和 S3 图床。如果你已经有自己的图床,可以直接继续使用,不需要迁移图片。

不想自己配置图床,也可以使用 MarkPress 提供的图片托管空间。插入图片、上传和替换链接都可以留在当前的写作流程里完成。

微信公众号排版

Markdown 写起来很轻松,但复制到微信公众号后台以后,标题、引用、代码块和段落样式经常要重新调整。

MarkPress 可以把 Markdown 转换成适合公众号后台的内联样式,目前提供 11 套排版模板。选择模板后复制内容,就可以继续进入公众号的发布流程。

它并不打算替代公众号后台,解决的只是最耗时间的那部分:避免把已经写好的文章再排一遍。

一键发布 WordPress

MarkPress 支持直接把 Markdown 原稿发布到 WordPress。

文章写完后,可以在 MarkPress 中填写标题、摘要、分类等信息,然后完成发布,不必再打开 WordPress 后台新建文章、复制内容、调整格式和重新处理图片。

这项功能目前仍处于 Beta 阶段,我还在继续处理不同主题、插件和 WordPress 配置带来的兼容问题。

把文章做成社交媒体卡片

一篇长文章写完以后,其中一些段落也适合单独整理成图片,发布到小红书、Instagram 或其他社交平台。

MarkPress 可以按照 Markdown 的内容结构生成分页图文卡片,并提供不同主题和自定义尺寸。生成后既可以逐页导出,也可以批量下载,支持 PNG、JPEG 和 WebP 等格式。

这样做的好处是,文字只需要整理一次。选好段落和主题后就可以生成图片,不用再复制到设计工具里从头排版。

MarkPress 现在支持什么

目前,MarkPress 已经覆盖了从写作到发布的几个主要环节:

  • Markdown 写作与实时预览
  • GFM 与代码高亮
  • 浏览器本地保存
  • 跨设备云同步
  • Markdown 文件导入与下载
  • S.EE、Cloudinary 和 S3 图床
  • 图片托管
  • 微信公众号排版
  • WordPress 发布
  • Markdown 分页卡片
  • 小红书、Instagram 等社交媒体图片
  • 自定义卡片尺寸与多种图片格式导出

这些功能看起来分成了编辑、图片、排版和发布几类,但它们解决的是同一件事:让一份 Markdown 原稿可以继续往下走,不必在不同工具之间反复复制。

还有一些功能正在路上

MarkPress 现在还没有完成。

WordPress 发布仍在 Beta 阶段。多平台一键发布、PDF、Word 和 ePub 导出,以及 Mermaid、LaTeX、自定义主题,目前都还在后续计划中。

我不想为了让产品介绍显得功能很多,就把路线图上的内容写成已经实现。比起一次放出一长串功能,我更在意现有流程是不是真的顺手。

我自己现在也每天使用 MarkPress。现阶段的目标很具体:写作时少一点打扰,写完后少切几个工具。

如果你平时会写 Markdown、博客或公众号,或者经常需要把同一份内容发布到不同平台,可以打开试试。

官网:markpress.pro

如果使用过程中遇到哪个步骤让你觉得“这里为什么还要操作一次”,欢迎告诉我。MarkPress 里现在的不少功能,就是从这种具体的麻烦里长出来的。

Excel 函数教程(9):如何使用 AVERAGEIFS 函数

1. 什么是 AVERAGEIFS 函数?

AVERAGEIFS 函数是 Microsoft Excel 电子表格软件中的一个功能强大的函数,用于根据多个条件计算满足条件的单元格平均值。它允许你在一个或多个范围内根据多个条件进行筛选,并计算满足所有条件的单元格的平均值。

2. AVERAGEIFS 函数的作用是什么?

AVERAGEIFS 函数的主要作用是根据多个条件计算单元格的平均值。这对于在数据分析和报告中汇总和计算特定条件下的平均值非常有用,使用户能够更好地了解数据的趋势和特征。

3. AVERAGEIFS 函数的基本语法和参数详细介绍

AVERAGEIFS 函数的基本语法如下:

AVERAGEIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...)

参数解释:

  • average_range:要计算平均值的范围。
  • criteria_range1:要检查的第一个范围。
  • criteria1:与第一个范围中的单元格进行比较的条件。
  • criteria_range2:(可选)要检查的第二个范围。
  • criteria2:(可选)与第二个范围中的单元格进行比较的条件。

可以根据需要添加更多的范围和条件对。

4. 给出 3 个使用 AVERAGEIFS 函数的示例

示例 1:课程成绩平均分统计

假设你有一个课程成绩表格,其中包含了学生姓名(A 列)、科目(B 列)和分数(C 列)。你想要计算某位学生在数学科目上得分超过 90 分的平均分。

A B C
姓名 科目 分数
小明 数学 85
小红 数学 92
小明 英语 78
小红 英语 88

在另一个单元格中使用以下 AVERAGEIFS 函数:

=AVERAGEIFS(C2:C5, A2:A5, "小红", B2:B5, "数学", C2:C5, ">90")

结果将是 92,表示小红在数学科目上得分超过 90 分的平均分为 92 分。

示例 2:销售额平均值统计

假设你有一个销售数据表格,其中包含了产品名称(A 列)、销售额(B 列)和销售日期(C 列)。你想要计算在某个特定月份内销售额超过 1000 的产品的平均销售额。

A B C
产品名 销售额 销售日期
产品 A 1200 2023-07-15
产品 B 800 2023-07-20
产品 C 1500 2023-08-05
产品 A 900 2023-08-10

在另一个单元格中使用以下 AVERAGEIFS 函数:

=AVERAGEIFS(B2:B5, C2:C5, ">=2023-08-01", C2:C5, "<=2023-08-31", B2:B5, ">1000")

结果将是 1500,表示在 2023 年 8 月份内销售额超过 1000 的产品的平均销售额为 1500。

示例 3:员工绩效评估

假设你有一个员工绩效评估表格,其中包含了员工姓名(A 列)、部门(B 列)和绩效得分(C 列)。你想要计算某个特定部门内绩效得分超过 80 分的员工的平均绩效。

A B C
姓名 部门 绩效
张三 销售 85
李四 技术 92
王五 销售 78
赵六 技术 88

在另一个单元格中使用以下 AVERAGEIFS 函数:

=AVERAGEIFS(C2:C5, B2:B5, "销售", C2:C5, ">80")

结果将是 85,表示销售部门内绩效得分超过 80 分的员工的平均绩效为 85 分。

通过这些示例,你可以更好地理解如何使用 AVERAGEIFS 函数来根据多个条件计算单元格的平均值。根据你的实际需求,可以根据 AVERAGEIFS 函数的语法和参数进行灵活的调整和使用。

原文 https://appsoftea.com/zh/excel-averageifs/

在线 PDF 转图片工具

背景

当今数字化时代,文档的处理和分享变得愈发便捷,然而在某些情况下,我们可能仍然需要将PDF文件转换为图片格式,以满足不同的需求。无论是出于文件保护的需要,还是为了更方便地分享和浏览,我们的在线PDF转图片工具将成为您的得力助手。

轻松转换,便捷操作

使用我们的PDF转图片工具,操作轻松简便。只需两步即可完成整个过程:

  1. 进入官网:https://lwebapp.com/zh/pdf-to-image

  2. 选择PDF文件:点击页面上的选择一份PDF文件按钮,或者直接将您的PDF文件拖拽到指定的虚线区域中。我们支持多种PDF文件,无论是电子书、报告还是表格,都能快速转换为图片格式。

  3. 下载图片:点击下载图片按钮,转换后的图片文件将立即为您准备就绪。您可以将其保存到您的设备中,随时随地轻松访问。

深入了解PDF格式

PDF,即Portable Document Format,意为“可携带文档格式”,是由Adobe Systems开发的一种与应用程序、操作系统、硬件无关的文件交换格式。它基于PostScript语言图像模型,无论在何种打印机上,都能保证精确的颜色和印刷效果,完美再现原稿的每个字符、颜色和图像。

转换为图片的优势

为什么需要将PDF文件转换为图片呢?以下是一些常见的需求和优势:

  1. 保护文件完整性:将PDF转换为图片后,文件内容变为不可编辑,有效防止未经授权的修改,确保文档的原始状态。

  2. 便于浏览:将文档转换为图片,可以将其存储在您的相册中,随时随地方便地浏览,无需特定的阅读工具。

  3. 分享社交平台:图片格式更适合分享到社交媒体平台,确保您的文档以更美观的方式展示给大众。

  4. 满足网站要求:某些网站要求上传图片格式的文档,此时将PDF转为图片能够轻松满足这一需求。

总结

我们的在线PDF转图片工具为您提供了一个高效、快捷的转换平台,让您在不同场景下更加灵活地处理文档。无论是保护文件内容,还是为了更好的分享和展示,我们都能满足您的需求。只需几个简单的步骤,即可完成转换,让您轻松应对各种文档处理挑战。

不管您是个人用户还是专业人士,我们都致力于为您提供最佳的PDF转图片解决方案。让我们的在线PDF转图片工具成为您的好帮手,让文件处理变得更加便捷高效。我们将不断优化和升级我们的工具,以满足您不断变化的需求

原文:https://appsoftea.com/zh/online-pdf-to-image-converter/

如何体验 ChatGPT

背景

最近群里都在聊 ChatGPT,实在太火了。给不知道的朋友科普下,ChatGPT 是一种基于 GPT 模型的人工智能聊天机器人,主要用于自然语言处
理领域,它能够根据用户输入自动生成聊天内容。

最好玩的是还可以帮你编程,调试 BUG。

写小作文发个推也是不在话下

连马斯克也玩的不亦乐乎

那么如何开始体验呢?

体验

  1. 首先要学会科学上网,这里不赘述,请参考这篇文章:

因为 ChatGPT 暂时只开放了少数国家的服务,推荐切换到美国或者印度,并开启全局模式。

  1. 注册国外手机号

不仅地区要切换到国外,手机号也有限制,目前大家推荐的方式就是接码平台购买手机号,按需付费就行,费用不会太贵,比如有名的 sms-activate,可以切换到中文界面了,使用非常方便

官网地址 sms-activate.org

只是体验一下的话,充值 1 美元就差不多了,然后买一个美国或者印度的手机号。

  1. 注册 ChatGPT

ChatGPT 官网地址

https://chat.openai.com/

点击 Sign up 注册就行,如果你买的印度的手机号,保证你全局代理到了印度,并且在手机号那里就填写你刚刚注册的手机号,就能接收到验证码了。

登入网站后,就可以开始和 ChatGPT 愉快的聊天了。

总结

因为 ChatGPT 太火了,导致访问的时候经常崩溃掉

大家可以先准备好科学上网工具和手机号,过段时间再访问。

原文:https://appsoftea.com/zh/chatgpt/

其他一些有用的关于ChatGPT的链接

git error: bad signature fatal: index file corrupt 解决方案

问题

写好代码准备使用 git desktop 提交的时候,发现我的项目提示 can’t find XXX (XXX 为项目名),并且使用 git bash 输入:git pull 会提示:

error: bad signature 
fatal: index file corrupt

然后尝试remove掉我的项目,重新 Add Repositories, 也无法识别我的本地项目

解决方案:

步骤1

项目根目录下打开 git bash ,输入:

rm -f .git/index

步骤2

接着输入:

git reset

参考自: https://appsoftea.com/zh/git-error-bad-signature-fatal-index-file-corrupt , 感谢!

如何在 Puppeteer 中使用 2captcha-solver 插件

背景

在之前的文章中,小编给大家介绍了一款好用的验证码服务 2Captcha,并且撰写了一篇干货教程和一篇测评。对 2Captcha 不了解的朋友,可以先阅读这两篇文章。

干货:Nodejs Playwright 2Captcha 验证码识别实现自动登陆

测评:验证码服务 2Captcha 测评 2022 | 原理 用途 入门

Continue reading…

谷歌网址检查 robots.txt 不是最新的

背景

小编最近把网站的移动端界面做了升级,加入了 AMP 功能,目的是提升移动端的浏览体验。但是这一升级带来了很多问题。

首先移动端广告没了(广告是这个小破站运营的根本,望朋友们谅解),其次升级也影响了网站的流量,因为Google Console后台提示了AMP出现一些非严重问题,必需的结构化数据元素有误。

Continue reading…

webrtc 屏幕录制

需求

我们在复现软件问题,或者做教学的时候,需要录屏,一般来说需要下载屏幕录制软件,经常还会带上水印,有没有一种可以不用安装软件在线录屏的方案呢,答案就是webrtc,使用浏览器自带的功能,打开网页就能录制桌面上的任意窗口,而且没有水印。

演示

https://dushusir.com/tool/web-recorder.html

代码

以下代码你将学习到
– 使用navigator.mediaDevices.getDisplayMedia获取用户窗口
– 使用MediaRecorder获取用户窗口录制的视频流
– 使用Blob和window.URL.createObjectURL来将视频流下载下来

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>webrtc screen recorder</title>
</head>

<body>


    <video autoplay playsinline id="player"></video>
    <video playsinline id="recordPlayer"></video>

    <!-- 在开始录制之前播放和下载按钮时禁用的 -->
    <button id="recordBtn">start</button>
    <button id="playBtn">play</button>
    <button id="downloadBtn">download</button>

    <script type="text/javascript">


        var player = document.querySelector("#player");
        var recordPlayer = document.querySelector("#recordPlayer");
        var recordBtn = document.querySelector("#recordBtn");
        var playBtn = document.querySelector("#playBtn");
        var downloadBtn = document.querySelector("#downloadBtn");

        var buffer; // 用于存储录制数据(数组)
        var mediaStream;
        var mediaRecoder;

        // 录制按钮点击事件
        recordBtn.addEventListener('click', function () {
            // console.log(recordBtn.text());
            if (recordBtn.textContent === 'start') {
                startRecord();
                recordBtn.textContent = 'stop';
                playBtn.setAttribute('disabled', true);
                downloadBtn.setAttribute('disabled', true);
            } else if (recordBtn.textContent === 'stop') {
                stopRecord();
                recordBtn.textContent = 'start';
                playBtn.removeAttribute('disabled');
                downloadBtn.removeAttribute('disabled');
            }
        });

        // 播放按钮点击事件
        playBtn.addEventListener('click', function () {
            var blob = new Blob(buffer, { type: 'video/webm' });
            // 根据缓存数据生成url给recordPlayer进行播放
            recordPlayer.src = window.URL.createObjectURL(blob);
            recordPlayer.srcObject = null;
            recordPlayer.controls = true; // 显示播放控件
        });

        // 下载按钮点击事件
        downloadBtn.addEventListener('click', function () {
            var blob = new Blob(buffer, { type: 'video/webm' });
            // 根据缓存数据生成url
            var url = window.URL.createObjectURL(blob);
            // 创建一个a标签,通过a标签指向url来下载
            var a = document.createElement('a');
            a.href = url;
            a.style.display = 'none'; // 不显示a标签
            a.download = 'test.webm'; // 下载的文件名
            a.click(); // 调用a标签的点击事件进行下载
        });

        // 开始录制
        function start() {

            var options = { mimeType: 'video/webm;codecs=vp8' };
            if (!MediaRecorder.isTypeSupported(options.mimeType)) {
                console.log('不支持' + options.mimeType);
                return;
            }

            try {
                buffer = [];
                mediaRecoder = new MediaRecorder(mediaStream, options);
            } catch (e) {
                console.log('创建MediaRecorder失败!');
                return;
            }
            mediaRecoder.ondataavailable = handleDataAvailable;
            // 开始录制,设置录制时间片为10ms(每10s触发一次ondataavilable事件)
            mediaRecoder.start(1000);
        }

        // 停止录制
        function stopRecord() {
            mediaRecoder.stop();
            mediaStream.getTracks().forEach(track => track.stop())
        }

        // 触发ondataavilable事件的回调函数
        function handleDataAvailable(e) {
            if (e && e.data && e.data.size > 0) {
                buffer.push(e.data);
            }
        }

        function startRecord() {
            if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
                console.log('不支采集音视频数据!');
            } else {
                // 采集音频数据
                var constrants = {
                    video: true,
                    audio: true
                };
                navigator.mediaDevices.getDisplayMedia(constrants).then(gotMediaStream).catch(handleError);

            }
        }

        // 采集音频数据成功时调用的方法
        function gotMediaStream(stream) {
            mediaStream = stream;
            player.srcObject = stream;

            start()
        }

        // 采集音频数据失败时调用的方法
        function handleError(err) {
            console.log(err.name + ':' + err.message);
        }
    </script>
</body>

</html>

新工具发布:keycode在线识别

需求

前端开发过程当中,我们有时候会需要监听键盘事件,比如使用document.onkeydown去监听,接着就要根据每个键盘的keycode来判断用户按了哪个键。

document.onkeydown = function (event) {
  var e = event || window.event || arguments.callee.caller.arguments[0];

  if (e && e.keyCode == 27) {
    // 按 Esc
    //要做的事情
  }

  if (e && e.keyCode == 113) {
    // 按 F2
    //要做的事情
  }

  if (e && e.keyCode == 13) {
    // enter 键
    //要做的事情
  }
};

为了找到keycode,通常我们都需要去搜索下,很麻烦。有没有快速查找这些keycode码的方法呢?

答案就是小编要推荐的这款工具,快速识键盘的keycode

工具

地址:https://dushusir.com/kcode/

使用方式:

  • 打开网页,直接敲击键盘,就能直接显示对应的keycode了
  • 点击右上角的table还能显示全部的按键对应的keycode,查找起来非常方便
  • 还可以切换暗黑和正常模式

总结

有任何疑问欢迎向作者反馈,更多小工具,欢迎访问:https://lwebapp.com/zh/tools