This commit is contained in:
c
2025-03-12 20:11:03 +08:00
commit 70b7c82c97
1178 changed files with 148185 additions and 0 deletions
@@ -0,0 +1,11 @@
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.BLOCK,
whitelist: ['left', 'right', 'center', 'justify']
}
const AlignStyle = new Attributor.Style('align', 'text-align', config)
return {
'formats/align': AlignStyle
}
}
@@ -0,0 +1,14 @@
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.BLOCK
}
const margin = ['margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight']
const padding = ['padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight']
const result = {}
margin.concat(padding).forEach(name => {
result[`formats/${name}`] = new Attributor.Style(name, name.replace(/[A-Z]/g, str => '-' + str.toLowerCase()), config)
})
return result
}
@@ -0,0 +1,12 @@
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.BLOCK,
whitelist: ['left', 'right', 'center', 'justify']
}
const AlignStyle = new Attributor.Style('align', 'text-align', config)
return {
'formats/align': AlignStyle
}
}
@@ -0,0 +1,4 @@
.divider hr {
border-color: rgba(0, 0, 0, .1);
border-width: 1px 0 0;
}
@@ -0,0 +1,23 @@
import './divider.css'
export default function (Quill) {
const BlockEmbed = Quill.import('blots/block/embed')
class Divider extends BlockEmbed {
static blotName = 'divider'
static tagName = 'hr'
static create (value) {
const node = super.create(value)
const wrap = document.createElement('div')
wrap.classList.add('divider')
wrap.setAttribute('contenteditable', 'false')
wrap.appendChild(node)
return wrap
}
}
return {
'formats/divider': Divider
}
}
@@ -0,0 +1,13 @@
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.INLINE
}
const font = ['font', 'fontSize', 'fontStyle', 'fontVariant', 'fontWeight', 'fontFamily']
const result = {}
font.forEach(name => {
result[`formats/${name}`] = new Attributor.Style(name, name.replace(/[A-Z]/g, str => '-' + str.toLowerCase()), config)
})
return result
}
@@ -0,0 +1,14 @@
.image-loading {
min-height: 50px;
margin-bottom: 15px;
}
.image-loading-inner {
background: #f5f5f5;
border-radius: 4px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
user-select: none;
}
@@ -0,0 +1,81 @@
import './image.css'
const ATTRIBUTES = [
'alt',
'height',
'width',
'data-custom',
'class',
'data-local'
]
export default function (Quill) {
const Image = Quill.import('formats/image')
const BlockEmbed = Quill.import('blots/block/embed')
class ExtendImageFormat extends Image {
static create (value) {
if (typeof value === 'string') {
value = {
src: value,
uploaded: false
}
}
const {src, uploaded} = value
const node = super.create(src)
if (uploaded) {
node.classList.add('uploaded')
}
return node
}
static formats (domNode) {
return ATTRIBUTES.reduce(function (formats, attribute) {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute)
}
return formats
}, {})
}
static sanitize (url) {
return url
}
format (name, value) {
if (ATTRIBUTES.indexOf(name) > -1) {
if (value) {
this.domNode.setAttribute(name, value)
} else {
this.domNode.removeAttribute(name)
}
} else {
super.format(name, value)
}
}
}
class ImageLoadingFormat extends BlockEmbed {
static create (value) {
const node = super.create(value)
node.setAttribute('contenteditable', false)
node.setAttribute('upload-id', value)
const loading = document.createElement('div')
loading.classList.add('image-loading-inner')
loading.innerText = '正在上传'
node.appendChild(loading)
return node
}
}
ImageLoadingFormat.blotName = 'image-loading'
ImageLoadingFormat.tagName = 'div'
ImageLoadingFormat.className = 'image-loading'
return {
'formats/image': ExtendImageFormat,
'formats/image-loading': ImageLoadingFormat,
}
}
@@ -0,0 +1,28 @@
import divider from './divider'
import align from './align'
import list from './list'
import box from './box'
import font from './font'
import text from './text'
import image from './image'
import link from './link'
import unlockContent from "./unlock-content"
import mediaVideo from "./media-video"
export function register (Quill) {
const formats = {
divider,
align,
list,
box,
font,
text,
image,
link,
unlockContent,
mediaVideo
}
const options = {}
Object.values(formats).forEach(value => Object.assign(options, value(Quill)))
Quill.register(options, true)
}
@@ -0,0 +1,13 @@
export default function (Quill) {
const Link = Quill.import('formats/link')
Link.sanitize = url => {
const anchor = document.createElement('a')
anchor.href = url
const protocol = anchor.href.slice(0, anchor.href.indexOf(':'))
return Link.PROTOCOL_WHITELIST.concat('file').indexOf(protocol) > -1 ? url : Link.SANITIZED_URL
}
return {
'formats/link': Link
}
}
@@ -0,0 +1,95 @@
export default function (Quill) {
const Parchment = Quill.import('parchment')
const Container = Quill.import('blots/container')
const ListItem = Quill.import('formats/list/item')
class List extends Container {
static create (value) {
const tagName = value === 'ordered' ? 'OL' : 'UL'
const node = super.create(tagName)
if (value === 'checked' || value === 'unchecked') {
node.setAttribute('data-checked', value === 'checked')
}
return node
}
static formats (domNode) {
if (domNode.tagName === 'OL') return 'ordered'
if (domNode.tagName === 'UL') {
if (domNode.hasAttribute('data-checked')) {
return domNode.getAttribute('data-checked') === 'true' ? 'checked' : 'unchecked'
} else {
return 'bullet'
}
}
return undefined
}
constructor (domNode) {
super(domNode)
const listEventHandler = (e) => {
if (e.target.parentNode !== domNode) return
const format = this.statics.formats(domNode)
const blot = Parchment.find(e.target)
if (format === 'checked') {
blot.format('list', 'unchecked')
} else if (format === 'unchecked') {
blot.format('list', 'checked')
}
}
domNode.addEventListener('click', listEventHandler)
}
format (name, value) {
if (this.children.length > 0) {
this.children.tail.format(name, value)
}
}
formats () {
// We don't inherit from FormatBlot
return { [this.statics.blotName]: this.statics.formats(this.domNode) }
}
insertBefore (blot, ref) {
if (blot instanceof ListItem) {
super.insertBefore(blot, ref)
} else {
const index = ref == null ? this.length() : ref.offset(this)
const after = this.split(index)
after.parent.insertBefore(blot, after)
}
}
optimize (context) {
super.optimize(context)
const next = this.next
if (next != null && next.prev === this &&
next.statics.blotName === this.statics.blotName &&
next.domNode.tagName === this.domNode.tagName &&
next.domNode.getAttribute('data-checked') === this.domNode.getAttribute('data-checked')) {
next.moveChildren(this)
next.remove()
}
}
replace (target) {
if (target.statics.blotName !== this.statics.blotName) {
const item = Parchment.create(this.statics.defaultChild)
target.moveChildren(item)
this.appendChild(item)
}
super.replace(target)
}
}
List.blotName = 'list'
List.scope = Parchment.Scope.BLOCK_BLOT
List.tagName = ['OL', 'UL']
List.defaultChild = 'list-item'
List.allowedChildren = [ListItem]
return {
'formats/list': List
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,92 @@
import './media-video.css'
const ATTRIBUTES = [
'poster',
'src',
'duration'
]
function mediaDuration (duration) {
if (!duration) return
const hours = Math.floor(duration / 3600)
const minutes = Math.floor((duration - hours * 3600) / 60)
const seconds = Math.floor(duration - hours * 3600 - minutes * 60)
return `${hours ? (hours < 10 ? '0' + hours : hours) + ':' : ''}${minutes ? (minutes < 10 ? '0' + minutes: minutes) + ':' : '00:'}${seconds < 10 ? '0' + seconds : seconds}`
}
export default function (Quill) {
const BlockEmbed = Quill.import('blots/block/embed')
class MediaVideo extends BlockEmbed {
static create (value) {
const node = super.create(value)
node.setAttribute('contenteditable', false)
node.setAttribute('data-src', value.src || '')
node.setAttribute('data-poster', value.poster || '')
node.setAttribute('data-duration', value.duration || '')
const poster = document.createElement('div')
poster.classList.add('ql-media-video-poster')
if (value.poster) {
poster.style.backgroundImage = `url(${value.poster})`
} else {
poster.innerText = '无视频封面'
poster.classList.add('no-poster')
}
const tip = document.createElement('div')
tip.classList.add('ql-media-video-tip')
tip.innerText = (value.duration ? `${mediaDuration(value.duration)} `: '') + '此处不支持视频播放'
const remove = document.createElement('div')
remove.classList.add('ql-media-video-remove')
node.appendChild(poster)
node.appendChild(tip)
node.appendChild(remove)
return node
}
static formats(domNode) {
return ATTRIBUTES.reduce(function(formats, attribute) {
if (domNode.dataset[attribute]) {
formats[attribute] = domNode.dataset[attribute] || '';
}
return formats;
}, {});
}
format(name, value) {
switch (name) {
case 'src':
this.domNode.setAttribute('data-src', value)
break
case 'poster':
this.domNode.setAttribute('data-poster', value)
if (value) {
this.domNode.querySelector('.ql-media-video-poster').style.backgroundImage = `url(${value})`
this.domNode.querySelector('.ql-media-video-poster').innerText = ''
this.domNode.querySelector('.ql-media-video-poster').classList.remove('no-poster')
} else {
this.domNode.querySelector('.ql-media-video-poster').innerText = '无视频封面'
this.domNode.querySelector('.ql-media-video-poster').classList.add('no-poster')
}
break
case 'duration':
this.domNode.setAttribute('data-duration', value)
this.domNode.querySelector('.ql-media-video-tip').innerText = (value ? `${mediaDuration(value)} `: '') + '此处不支持视频播放'
break
default:
super.format(name, value)
}
}
}
MediaVideo.blotName = 'mediaVideo'
MediaVideo.className = 'ql-media-video'
MediaVideo.tagName = 'div'
return {
'formats/mediaVideo': MediaVideo,
}
}
@@ -0,0 +1,10 @@
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.BLOCK
}
const textIndentStyle = new Attributor.Style('text', 'text-indent', config)
return {
'formats/textIndent1': textIndentStyle
}
}
@@ -0,0 +1,24 @@
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const text = [{
name: 'lineHeight',
scope: Scope.BLOCK
}, {
name: 'letterSpacing',
scope: Scope.INLINE
}, {
name: 'textDecoration',
scope: Scope.INLINE
}, {
name: 'textIndent',
scope: Scope.BLOCK
}]
const result = {}
text.forEach(({ name, scope }) => {
result[`formats/${name}`] = new Attributor.Style(name, name.replace(/[A-Z]/g, str => '-' + str.toLowerCase()), {
scope
})
})
return result
}
@@ -0,0 +1,20 @@
.ql-unlock-content {
width: 100%;
padding: 20px 0;
font-size: 17px;
font-weight: bold;
text-align: center;
box-sizing: border-box;
cursor: default;
margin-bottom: 10px;
background: #f8f8f8;
border-radius: 5px;
color: #888;
}
.ql-unlock-content span {
user-select: none;
}
.ql-unlock-content a {
color: #269939;
margin-left: 5px;
}
@@ -0,0 +1,27 @@
import './unlock-content.css'
export default function UnlockContent () {
const BlockEmbed = Quill.import('blots/block/embed')
class UnlockContent extends BlockEmbed {
static blotName = 'unlockContent'
static tagName = 'div'
static className = 'ql-unlock-content'
static create (value) {
const node = super.create(value);
const span = document.createElement('span')
node.setAttribute('contenteditable', 'false')
span.innerHTML = `<span>以下内容需解锁后查看,广告解锁功能详见</span><a href="https://uniapp.dcloud.net.cn/uniCloud/uni-cms.html#watch-ad-unlock-content" target="_blank">文档</a>`
node.appendChild(span)
return node;
}
}
return {
'formats/unlockContent': UnlockContent
}
}