Quill文档(四):使用Parchment克隆Medium

为了提供一致的编辑体验,您需要一致的数据和可预测的行为。不幸的是,DOM缺乏这两个特性。现代编辑器的解决方案是维护自己的文档模型来表示它们的内容。对于Quill来说,Parchment就是这样的解决方案。它在自己的代码库中组织,并拥有自己的API层。通过Parchment,您可以定制Quill识别的内容和格式,或者添加全新的内容和格式。

在本指南中,我们将使用Parchment和Quill提供的基本构建块来复制媒体上的编辑器。我们将从没有任何主题、额外模块或格式的Quill的骨架开始。在这个基本层面上,Quill只理解纯文本。但通过本指南的结尾,链接、视频,甚至推文都将被理解。

基础工作


让我们从甚至不使用Quill开始,只用一个textarea和一个按钮,连接到一个虚拟事件监听器。我们将在整个指南中为了方便使用jQuery,但Quill或Parchment并不依赖于此。我们还将添加一些基本样式,借助Google Fonts和Font Awesome。这些与Quill或Parchment无关,所以我们将快速通过。

index.html

<link href="/styles.css" rel="stylesheet"><div id="tooltip-controls"><button id="bold-button"><i class="fa fa-bold"></i></button><button id="italic-button"><i class="fa fa-italic"></i></button><button id="link-button"><i class="fa fa-link"></i></button><button id="blockquote-button"><i class="fa fa-quote-right"></i></button><button id="header-1-button"><i class="fa fa-header"><sub>1</sub></i></button><button id="header-2-button"><i class="fa fa-header"><sub>2</sub></i></button>
</div>
<div id="sidebar-controls"><button id="image-button"><i class="fa fa-camera"></i></button><button id="video-button"><i class="fa fa-play"></i></button><button id="tweet-button"><i class="fa fa-twitter"></i></button><button id="divider-button"><i class="fa fa-minus"></i></button>
</div><textarea id="editor">Tell your story...</textarea><script type="module" src="/index.js"></script>

styles.css

#editor {display: block;font-family: 'Open Sans', Helvetica, sans-serif;font-size: 1.2em;height: 180px;margin: 0 auto;width: 450px;
}#tooltip-controls, #sidebar-controls {text-align: center;
}button {background: transparent;border: none;cursor: pointer;display: inline-block;font-size: 18px;padding: 0;height: 32px;width: 32px;text-align: center;
}
button:active, button:focus {outline: none;
}

index.js

document.querySelectorAll('button').forEach((button) => {button.addEventListener('click', () => {alert('Click!');});
});

添加Quill核心


接下来,我们将用没有主题、格式和额外模块的Quill核心替换textarea。打开您的开发者控制台,在您输入编辑器时检查演示。您可以看到Parchment文档的基本构建块在工作。

index.html

<link href="https://cdn.jsdelivr.net/npm/quill@2.0.0-rc.4/dist/quill.core.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.0-rc.4/dist/quill.core.js"></script><link href="/styles.css" rel="stylesheet"><div id="tooltip-controls"><button id="bold-button"><i class="fa fa-bold"></i></button><button id="italic-button"><i class="fa fa-italic"></i></button><button id="link-button"><i class="fa fa-link"></i></button><button id="blockquote-button"><i class="fa fa-quote-right"></i></button><button id="header-1-button"><i class="fa fa-header"><sub>1</sub></i></button><button id="header-2-button"><i class="fa fa-header"><sub>2</sub></i></button>
</div>
<div id="sidebar-controls"><button id="image-button"><i class="fa fa-camera"></i></button><button id="video-button"><i class="fa fa-play"></i></button><button id="tweet-button"><i class="fa fa-twitter"></i></button><button id="divider-button"><i class="fa fa-minus"></i></button>
</div><div id="editor">Tell your story...</div><script type="module" src="/index.js"></script>

styles.css

#editor {border: 1px solid #ccc;font-family: 'Open Sans', Helvetica, sans-serif;font-size: 1.2em;height: 180px;margin: 0 auto;width: 450px;
}#tooltip-controls, #sidebar-controls {text-align: center;
}button {background: transparent;border: none;cursor: pointer;display: inline-block;font-size: 18px;padding: 0;height: 32px;width: 32px;text-align: center;
}
button:active, button:focus {outline: none;
}

index.js

document.querySelectorAll('button').forEach((button) => {button.addEventListener('click', () => {alert('Click!');});
});const quill = new Quill('#editor');

与DOM类似,Parchment文档是一个树形结构。它的节点,称为Blots,是对DOM节点的抽象。对于我们来说,已经定义了一些基本的Blots:Scroll(滚动)、Block(块)、Inline(内联)、Text(文本)和Break(断点)。当您输入时,Text blot会与对应的DOM Text节点同步;按下回车键则通过创建一个新的Block blot来处理。在Parchment中,能够拥有子节点的Blots至少必须有一个子节点,因此空的Blocks会填充一个Break blot。这使得处理叶子节点变得简单且可预测。所有这些都组织在一个根Scroll blot下。

在这个阶段,您无法仅通过打字来观察Inline blot,因为它不会对文档产生有意义的结构或格式。一个有效的Quill文档必须是规范且紧凑的。能够表示给定文档的只有一个有效的DOM树,而这个DOM树包含最小数量的节点。

由于<p><span>Text</span></p><p>Text</p>表示相同的内容,前者是无效的,解包<span>是Quill优化过程的一部分。类似地,一旦我们添加了格式,<p><em>Te</em><em>st</em></p><p><em><em>Test</em></em></p>也是无效的,因为它们不是最紧凑的表示形式。

由于这些限制,Quill不能支持任意的DOM树和HTML更改。但正如我们将看到的,这种结构提供的一致性和可预测性使我们能够轻松构建丰富的编辑体验。

基本格式


我们之前提到,内联(Inline)不贡献格式。这是基本情况的例外,而不是规则,为基本的内联类而设。基本的块级(Block)Blot与块级元素的工作方式相同。

为了实现加粗和斜体,我们只需要从Inline继承,设置blotNametagName,并将其注册到Quill。有关继承和静态方法和变量签名的完整参考,请查看Parchment。

const Inline = Quill.import('blots/inline');class BoldBlot extends Inline {static blotName = 'bold';static tagName = 'strong';
}class ItalicBlot extends Inline {static blotName = 'italic';static tagName = 'em';
}Quill.register(BoldBlot);
Quill.register(ItalicBlot);

我们在这里遵循Medium的示例使用strongem标签,但您也可以使用bi标签。Blot的名称将被用作Quill的格式名称。通过注册我们的Blot,我们现在可以使用Quill的完整API在我们的新格式上:

在Quill富文本编辑器中,Blot是构建编辑器内容的基本单位。每个Blot代表文档中的一个可编辑元素,比如一个段落、一个图片或一个文本格式(例如加粗或斜体)。Quill利用一个富文本模型(Parchment)来定义和管理这些Blots,它们共同构成了编辑器的文档模型。

Blot名称和格式名称

当定义一个自定义Blot时,该Blot的名称非常重要,因为它直接关联到Quill的格式名称。这意味着:

  • Blot名称的定义:在创建一个自定义Blot时,你需要为它指定一个唯一的名称。这个名称在Quill编辑器中用于引用该格式。
  • 作为格式名称使用:在编辑器中应用格式时,使用的格式名称实际上就是定义Blot时所指定的名称。例如,如果你创建了一个自定义Blot,命名为myCustomFormat,那么在通过API应用这个格式到选中的文本时,将使用这个名称:quill.format('myCustomFormat', true);

这条命令会应用myCustomFormat格式到当前选中的文本或插入点。在实际效果中,这意味着Quill会创建一个MyCustomBlot的实例,并将其插入到文档中,从而将选中的文本包裹或标记为myCustomFormat格式。

Quill.register(BoldBlot);
Quill.register(ItalicBlot);const quill = new Quill('#editor');quill.insertText(0, 'Test', { bold: true });
quill.formatText(0, 4, 'italic', true);
// 如果我们将斜体Blot命名为"myitalic",我们将调用
// quill.formatText(0, 4, 'myitalic', true);

让我们摆脱我们的虚拟按钮处理程序,并将加粗和斜体按钮连接到Quill的format()。为了简单起见,我们将硬编码为true,始终添加格式。在您的应用程序中,您可以使用getFormat()来检索任意范围的当前格式,以决定是否添加或删除格式。工具栏模块为Quill实现了这一点,我们将在这里重新实现它。

链接


链接稍微复杂一些,因为我们存储链接url需要不仅仅是一个布尔值。这在两个方面影响我们的链接Blot:创建和格式检索。我们将url表示为字符串值,但我们也可以以其他方式表示,例如具有url键的对象,允许设置其他键/值对来定义链接。我们将在稍后使用图像演示这一点。

class LinkBlot extends Inline {static blotName = 'link';static tagName = 'a';static create(value) {const node = super.create();// 如果需要,对url值进行消毒node.setAttribute('href', value);// 可以设置其他非格式相关属性// 这些对Parchment是不可见的,所以必须是静态的node.setAttribute('target', '_blank');return node;}static formats(node) {// 我们只会被调用,使用已经// 确定为链接Blot的节点,所以我们// 不需要检查自己return node.getAttribute('href');}
}Quill.register(LinkBlot);

现在我们可以将我们的链接按钮连接到一个花哨的提示,再次为了保持简单,然后传递给Quill的format()

引用和标题


引用(Blockquotes)的实现方式与加粗Blot相同,只不过我们将从块级(Block)基础Blot继承。虽然内联(Inline)Blot可以嵌套,但块级Blot不能。当应用于相同的文本范围时,块级Blot不会包裹,而是相互替换。

const Block = Quill.import('blots/block');class BlockquoteBlot extends Block {static blotName = 'blockquote';static tagName = 'blockquote';
}

标题的实现方式完全相同,只有一个区别:它可以由多个DOM元素表示。格式的值默认成为tagName,而不仅仅是true。我们可以通过扩展formats()来自定义这一点,就像我们对链接所做的那样。

class HeaderBlot extends Block {static blotName = 'header';// Medium只支持两个标题大小,所以我们只演示两个,// 但我们很容易就在这个数组中添加更多的标签static tagName = ['H1', 'H2'];static formats(node) {return HeaderBlot.tagName.indexOf(node.tagName) + 1;}
}

让我们将这些新的Blot连接到它们各自的按钮,并为<blockquote>标签添加一些CSS。

分隔线


现在让我们实现我们的第一个叶子Blot。虽然我们之前的Blot示例贡献了格式(formatting)并实现了format(),但叶子Blot贡献了内容并实现了value()叶子Blot可以是文本或嵌入(Embed)Blot,所以我们的章节分隔线将是一个嵌入。一旦创建,嵌入Blot的值就是不可变的,需要删除和重新插入来改变该位置的内容。

我们的方法与之前类似,只不过我们从块级嵌入(BlockEmbed)继承。嵌入(Embed)也存在于blots/embed下,但那是为内联级Blot设计的。我们希望对分隔线使用块级实现。

const BlockEmbed = Quill.import('blots/block/embed');class DividerBlot extends BlockEmbed {static blotName = 'divider';static tagName = 'hr';
}

我们的点击处理程序调用insertEmbed(),它不像format()那样方便地确定、保存和恢复用户选择,所以我们不得不做更多的工作来自己保留选择。此外,当我们尝试在块中间插入一个块级嵌入时,Quill会为我们分割块。为了使这种行为更清晰,我们将通过在插入分隔线之前插入一个新行来明确地分割块。

index.html

<link href="https://cdn.jsdelivr.net/npm/quill@2.0.0-rc.4/dist/quill.core.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.0-rc.4/dist/quill.core.js"></script><link href="/styles.css" rel="stylesheet"><div id="tooltip-controls"><button id="bold-button"><i class="fa fa-bold"></i></button><button id="italic-button"><i class="fa fa-italic"></i></button><button id="link-button"><i class="fa fa-link"></i></button><button id="blockquote-button"><i class="fa fa-quote-right"></i></button><button id="header-1-button"><i class="fa fa-header"><sub>1</sub></i></button><button id="header-2-button"><i class="fa fa-header"><sub>2</sub></i></button>
</div>
<div id="sidebar-controls"><button id="image-button"><i class="fa fa-camera"></i></button><button id="video-button"><i class="fa fa-play"></i></button><button id="tweet-button"><i class="fa fa-twitter"></i></button><button id="divider-button"><i class="fa fa-minus"></i></button>
</div><div id="editor">Tell your story...</div><script type="module" src="/index.js"></script>

dividerBlot.js

const BlockEmbed = Quill.import('blots/block/embed');class DividerBlot extends BlockEmbed {static blotName = 'divider';static tagName = 'hr';
}Quill.register(DividerBlot);

index.js

import './formats/boldBlot.js';
import './formats/italicBlot.js';
import './formats/linkBlot.js';
import './formats/blockquoteBlot.js';
import './formats/headerBlot.js';
import './formats/dividerBlot.js';const onClick = (selector, callback) => {document.querySelector(selector).addEventListener('click', callback);
};onClick('#bold-button', () => {quill.format('bold', true);
});onClick('#italic-button', () => {quill.format('italic', true);
});onClick('#link-button', () => {const value = prompt('Enter link URL');quill.format('link', value);
});onClick('#blockquote-button', () => {quill.format('blockquote', true);
});onClick('#header-1-button', () => {quill.format('header', 1);
});onClick('#header-2-button', () => {quill.format('header', 2);
});onClick('#divider-button', () => {const range = quill.getSelection(true);quill.insertText(range.index, '\n', Quill.sources.USER);quill.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER);quill.setSelection(range.index + 2, Quill.sources.SILENT);
});const quill = new Quill('#editor');

图片


图片可以使用我们在构建链接和分隔线Blot时学到的知识添加。我们将使用一个对象来表示值,以展示这种支持。我们插入图片的按钮处理程序将使用一个静态值,因此我们不会被与Parchment无关的提示UI代码分散注意力,Parchment是本指南的重点。

const BlockEmbed = Quill.import('blots/block/embed');class ImageBlot extends BlockEmbed {static blotName = 'image';static tagName = 'img';static create(value) {const node = super.create();node.setAttribute('alt', value.alt);node.setAttribute('src', value.url);return node;}static value(node) {return {alt: node.getAttribute('alt'),url: node.getAttribute('src')};}
}
Quill.register(ImageBlot);

<button id="image-button"><i class="fa fa-camera"></i></button><div id="editor">Tell your story...</div><script>onClick('#image-button', () => {const range = quill.getSelection(true);quill.insertText(range.index, '\n', Quill.sources.USER);quill.insertEmbed(range.index + 1, 'image', {alt: 'Quill Cloud',url: 'https://quilljs.com/0.20/assets/images/cloud.png'}, Quill.sources.USER);quill.setSelection(range.index + 2, Quill.sources.SILENT);
});const quill = new Quill('#editor');
</script>

视频


我们将以与图片类似的方式实现视频。我们可以使用HTML5的<video>标签,但我们不能用这种方式播放YouTube视频,由于这可能是更常见和相关的用例,我们将使用<iframe>来支持这一点。我们在这里不需要这样做,但如果您希望多个Blot使用相同的标签(tag),您可以在下一个推文示例中使用className以及tagName

此外,我们将添加对宽度和高度的支持,作为未注册的格式。特定于嵌入的格式不需要单独注册,只要没有与注册格式的命名空间冲突即可。这之所以有效,是因为Blot只是将未知格式传递给它的子元素,最终到达叶子。这也允许不同的嵌入以不同的方式处理未注册格式。例如,我们之前的image嵌入可以以与此处的video不同的方式识别和处理宽度格式。

class VideoBlot extends BlockEmbed {static blotName = 'video';static tagName = 'iframe';static create(url) {const node = super.create();node.setAttribute('src', url);// 使用静态值设置非格式相关属性node.setAttribute('frameborder', '0');node.setAttribute('allowfullscreen', true);return node;}static formats(node) {// 我们仍然需要报告未注册的嵌入格式const format = {};if (node.hasAttribute('height')) {format.height = node.getAttribute('height');}if (node.hasAttribute('width')) {format.width = node.getAttribute('width');}return format;}static value(node) {return node.getAttribute('src');}format(name, value) {// 处理未注册的嵌入格式if (name === 'height' || name === 'width') {if (value) {this.domNode.setAttribute(name, value);} else {this.domNode.removeAttribute(name, value);}} else {super.format(name, value);}}
}Quill.register(VideoBlot);

请注意,如果您打开控制台并调用getContents,Quill将报告视频如下:

{ops: [{insert: {video: 'https://www.youtube.com/embed/QHH3iSeDBLo?showinfo=0&'},attributes: {height: '170',width: '400'}}]
}

<button id="video-button"><i class="fa fa-play"></i></button><div id="editor">Tell your story...</div><script>onClick('#video-button', () => {let range = quill.getSelection(true);quill.insertText(range.index, '\n', Quill.sources.USER);let url = 'https://www.youtube.com/embed/QHH3iSeDBLo?showinfo=0';quill.insertEmbed(range.index + 1, 'video', url, Quill.sources.USER);quill.formatText(range.index + 1, 1, { height: '170', width: '400' });quill.setSelection(range.index + 2, Quill.sources.SILENT);
});const quill = new Quill('#editor');
</script>

推特


Medium支持许多嵌入类型,但我们将只关注本指南中的推文。推文Blot几乎与图片完全相同实现。我们利用了嵌入Blot不必对应一个空节点的事实。它可以是任何任意节点,Quill将其视为空节点,不会遍历其子元素或后代。这使我们能够使用<div>和原生Twitter JavaScript库在指定的<div>容器内做它想做的事情。

由于我们的根滚动Blot也使用<div>,我们还指定了一个className来消除歧义。请注意,内联Blot默认使用<span>,块级Blot默认使用<p>,所以如果您想为您的自定义Blot使用这些标签,您必须指定className以及tagName

我们使用推文id作为定义我们Blot的值。再次,我们的点击处理程序使用一个静态值,以避免分散注意力到无关的UI代码上。

class TweetBlot extends BlockEmbed {static blotName = 'tweet';static tagName = 'div';static className = 'tweet';static create(id) {const node = super.create();node.dataset.id = id;// 允许twitter库修改我们的内容twttr.widgets.createTweet(id, node);return node;}static value(domNode) {return domNode.dataset.id;}
}Quill.register(TweetBlot);

<button id="tweet-button"><i class="fa fa-twitter"></i></button>
<div id="editor">Tell your story...</div><script>onClick('#tweet-button', () => {const range = quill.getSelection(true);const id = '464454167226904576';quill.insertText(range.index, '\n', Quill.sources.USER);quill.insertEmbed(range.index + 1, 'tweet', id, Quill.sources.USER);quill.setSelection(range.index + 2, Quill.sources.SILENT);
});const quill = new Quill('#editor');
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/788304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 PyArmor 加密一个 Python 包

如果你想要使用 PyArmor 加密一个 Python 包&#xff08;也就是一个包含多个模块的目录&#xff09;&#xff0c;你可以按照以下步骤进行&#xff1a; 安装 PyArmor&#xff1a;首先确保 PyArmor 已经被安装在你的环境中。如果未安装&#xff0c;可以通过 pip 安装&#xff1a;…

elment UI el-date-picker 月份组件选定后提交后台页面显示正常,提交后台字段变成时区格式

需求&#xff1a;要实现一个日期的月份选择<el-date-picker :typeformData.dateType :value-formatdateFormat v-modelformData.leaveFactoryDateplaceholder选择月份></el-date-picker>错误示例&#xff1a;将日期显示类型(type)dateType或将日期绑定值的格式(val…

LabVIEW专栏三、探针和断点

探针和断点是LabVIEW调试的常用手段&#xff0c;该节以上一节的"测试耗时"为例 探针可以打在有线条的任何地方&#xff0c;打上后&#xff0c;经过这条线的所有最后一次的数值都会显示在探针窗口。断点可以打在程序框图的所有G代码对象&#xff0c;包括结构&#xf…

【stm32】USART编码部分--串口数据包

USART串口数据包【源码放在最后】 关于数据包的分类 关于数据包的发送 如果想要发送Hex数据包&#xff0c;定义一个数组填充数据&#xff0c;然后使用串口模块函数SendArray进行发送 如果想要发送文本数据包&#xff0c;写一个字符串然后调用SendString进行发送 对于发送数据…

Spark 起源发展与项目架构说明

文章目录 前言Spark 的起源Spark 是什么速度易用性模块化可扩展性 分析方法的统一Spark SQLSpark MLlibSpark Structured StreamingGraphX Spark的分布式执行Spark driverSparkSessionCluster managerSpark executor部署模式分布式数据和分区 开发的经验Spark 的使用人群与使用…

关于其他服务器篡改请求头导致登录失效

问题描述 此问题是单点登录转发问题&#xff0c;客户服务器域名访问一个本程序对外接口获取token&#xff0c;并跳转至本系统登录页面&#xff0c;在网关日志中发现token为空的异常。 问题排查 1、拿token在postman中发送&#xff0c;发现请求是成功的&#xff0c;本程序通过n…

App测试中ios和Android的区别

1、Android长按home键呼出应用列表和切换应用&#xff0c;然后右滑则终止应用&#xff1b; 2、多分辨率测试&#xff0c;Android端20多种&#xff0c;ios较少&#xff1b; 3、手机操作系统&#xff0c;Android较多&#xff0c;ios较少且不能降级&#xff0c;只能单向升级&…

InfluxDB2的数据查询示例

有用influxdb2 不支持sql&#xff0c;并且实质是个列存储数据库&#xff0c;这里基于 influxdb-client-java 和 beanutils反射&#xff0c;写了个数据查询&#xff0c;把结果以行对象的形式返回的工具类。 package com.joy.malltools.influxdb2;import com.influxdb.client.Q…

越南工厂连接中国总部服务器解决方案---案例分享

随着全球化的不断深入&#xff0c;许多中国企业走出国门&#xff0c;在世界各地设立分支机构和生产基地。然而&#xff0c;随之而来的是跨国网络通信的挑战。近期&#xff0c;客户越南的工厂与中国总部之间的网络连接出现了一些问题&#xff0c;这直接影响了企业的日常运营效率…

vscode shadertoy插件,非常方便的glsl着色器编写工具

很著名的shadertoy网站&#xff0c;集合了非常多大神利用数学写出美妙的shader效果。像shadertoy创始人之一的IQ大神它在这方面有很多的建树。他的利用光线步进和躁声可以创建很多不可思议的3D场景。 vscode有一件shadertoy的插件&#xff0c;安装后可以新建一个*.glsl文件&am…

使用 FinalShell 进行远程连接(ssh 远程连接 Linux 服务器)

目录 前言 基本使用教程 新建远程连接 连接主机 自定义命令 路由追踪 前言 后端开发&#xff0c;必然需要和服务器打交道&#xff0c;部署应用&#xff0c;排查问题&#xff0c;查看运行日志等等。一般服务器都是集中部署在机房中&#xff0c;也有一些直接是云服务器&am…

Synchronized锁升级过程

无锁-->偏向锁---> 轻量级锁---->重量级锁 ①、从无锁到偏向锁&#xff1a; 当一个线程首次访问同步块时&#xff0c;如果此对象无锁状态且偏向锁未被禁用&#xff0c;JVM 会将该对象头的锁标记改为偏向锁状态&#xff0c;并记录下当前线程的 ID。此时&#xff0c;对…

IntelliJ IDEA - 快捷键 Win Mac 对照表

基本 Ctrl CommandAlt Option WinMacCtrl YCommand DeleteCtrl WOption 方向键上Ctrl NCommand OCtrl F11Option F3Shift F11Command F3Ctrl SpaceControl SpaceCtrl 方向键Option 方向键Alt Control VAlt EnterOption EnterAlt InsertCommand NAlt 方…

Flume学习笔记

视频地址:https://www.bilibili.com/video/BV1wf4y1G7EQ/ 定义 Flume是一个高可用的、高可靠的、分布式的海量日志采集、聚合和传输的系统。 Flume高最要的作用就是实时读取服务器本地磁盘的数据,将数据写入HDFS。 官网:https://flume.apache.org/releases/content/1.9.0/…

云智慧发布对象关系型数据库CloudPanguDB,打破传统技术壁垒

近日&#xff0c;云智慧推出关系型数据库CloudPanguDB&#xff08;中文名称&#xff1a;盘古数据库&#xff09;&#xff0c;旨在通过高兼容性能和创新技术架构&#xff0c;降低企业项目整体运营成本。 无论是处理海量复杂数据&#xff0c;还是构建清晰有序的数据结构关系&…

练习 17 Web [极客大挑战 2019]PHP

常见的网站源码备份文件名和后缀&#xff0c;反序列化攻击 unserialize()&#xff1a;wakeup绕过&#xff0c;private类以及属性序列化后的%00修改 开靶机 提到”备份“ 那看看有没有backup.php啥的 如果网站存在备份文件&#xff0c;常见的备份文件后缀名有&#xff1a;“.gi…

嵌入式数据库-Sqlite3

阅读引言&#xff1a; 本文将会从环境sqlite3的安装、数据库的基础知识、sqlite3命令、以及sqlite的sql语句最后还有一个完整的代码实例&#xff0c; 相信仔细学习完这篇内容之后大家一定能有所收获。 目录 一、数据库的基础知识 1.数据库的基本概念 2.常用数据库 3.嵌入式…

Qt中的OpenGL

一、OpenGL简介 1.1什么是OpenGL Open Graphics Library&#xff0c;它是一个由Khronos组织制定并维护的规范(Specification)OpenGL核心是一个C库&#xff0c;同时也支持多种语言的派生 1.2 核心模式&#xff08;Core-profile&#xff09; 也叫可编程管线&#xff0c;提供了…

网络安全应急响应:保护网络安全的最后一道防线

网络安全应急响应&#xff1a;保护网络安全的最后一道防线 网络安全是当今信息社会中至关重要的问题&#xff0c;网络攻击的频繁发生使得企业、政府和个人面临着越来越大的安全威胁。为了及时有效地应对网络安全事件&#xff0c;网络安全应急响应成为了必不可少的一环。 小德将…

WPF中继承ItemsControl子类控件数据模板获取选中属性

需求场景 列表类控件&#xff0c;如 ListBox、ListView、DataGrid等。显示的行数据中&#xff0c;部分内容依靠选中时触发控制&#xff0c;例如选中行时行记录复选&#xff0c;部分列内容控制显隐。 案例源码以ListView 为例。 Xaml 部分 <ListView ItemsSource"{Bi…