请求解析
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
//CssTokenizer.js
|
||||
function CssTokenizer(style = '', tagStyle = {}) {
|
||||
this.res = tagStyle;
|
||||
this._state = "SPACE";
|
||||
this._buffer = style;
|
||||
this._sectionStart = 0;
|
||||
this._index = 0;
|
||||
this._name = '';
|
||||
this._content = '';
|
||||
this._list = [];
|
||||
this._comma = false;
|
||||
}
|
||||
CssTokenizer.prototype.SPACE = function(c) {
|
||||
if (/[a-zA-Z.#]/.test(c)) {
|
||||
this._sectionStart = this._index;
|
||||
this._state = "InName";
|
||||
} else if (c == '@') this._state = "Ignore1";
|
||||
else if (c == '/') this._state = "BeforeComment";
|
||||
};
|
||||
CssTokenizer.prototype.BeforeComment = function(c) {
|
||||
if (c == '*') this._state = "InComment";
|
||||
else {
|
||||
this._index--;
|
||||
this._state = "SPACE";
|
||||
}
|
||||
};
|
||||
CssTokenizer.prototype.InComment = function(c) {
|
||||
if (c == '*') this._state = "AfterComment";
|
||||
};
|
||||
CssTokenizer.prototype.AfterComment = function(c) {
|
||||
if (c == '/') this._state = "SPACE";
|
||||
else {
|
||||
this._index--;
|
||||
this._state = "InComment"
|
||||
}
|
||||
};
|
||||
CssTokenizer.prototype.InName = function(c) {
|
||||
if (c == '{') {
|
||||
this._list.push(this._buffer.substring(this._sectionStart, this._index))
|
||||
this._sectionStart = this._index + 1;
|
||||
this._state = "InContent";
|
||||
} else if (c == ',') {
|
||||
this._list.push(this._buffer.substring(this._sectionStart, this._index));
|
||||
this._sectionStart = this._index + 1;
|
||||
this._comma = true;
|
||||
} else if ((c == '.' || c == '#') && !this._comma) {
|
||||
this._buffer = this._buffer.splice(this._index, 1, ' ');
|
||||
} else if (/\s/.test(c)) {
|
||||
this._name = this._buffer.substring(this._sectionStart, this._index);
|
||||
this._state = "NameSpace";
|
||||
} else if (/[>:\[]/.test(c)) {
|
||||
if (this._list.length) this._state = "IgnoreName";
|
||||
else this._state = "Ignore1";
|
||||
} else this._comma = false;
|
||||
};
|
||||
CssTokenizer.prototype.NameSpace = function(c) {
|
||||
if (c == '{') {
|
||||
this._list.push(this._name);
|
||||
this._sectionStart = this._index + 1;
|
||||
this._state = "InContent";
|
||||
} else if (c == ',') {
|
||||
this._comma = true;
|
||||
this._list.push(this._name);
|
||||
this._sectionStart = this._index + 1;
|
||||
this._state = "InName"
|
||||
} else if (/\S/.test(c)) {
|
||||
if (this._comma) {
|
||||
this._sectionStart = this._index;
|
||||
this._index--;
|
||||
this._state = "InName";
|
||||
} else if (this._list.length) this._state = "IgnoreName";
|
||||
else this._state = "Ignore1"
|
||||
}
|
||||
};
|
||||
CssTokenizer.prototype.InContent = function(c) {
|
||||
if (c == '}') {
|
||||
this._content = this._buffer.substring(this._sectionStart, this._index);
|
||||
for (let item of this._list)
|
||||
this.res[item] = (this.res[item] || '') + this._content;
|
||||
this._list = [];
|
||||
this._comma = false;
|
||||
this._state = "SPACE";
|
||||
}
|
||||
};
|
||||
CssTokenizer.prototype.IgnoreName = function(c) {
|
||||
if (c == ',') {
|
||||
this._sectionStart = this._index + 1;
|
||||
this._state = "InName";
|
||||
} else if (c == '{') {
|
||||
this._sectionStart = this._index + 1;
|
||||
this._state = "InContent";
|
||||
}
|
||||
}
|
||||
CssTokenizer.prototype.Ignore1 = function(c) {
|
||||
if (c == ';') {
|
||||
this._state = "SPACE";
|
||||
this._sectionStart = this._index + 1;
|
||||
} else if (c == '{') this._state = "Ignore2";
|
||||
};
|
||||
CssTokenizer.prototype.Ignore2 = function(c) {
|
||||
if (c == '}') {
|
||||
this._state = "SPACE";
|
||||
this._sectionStart = this._index + 1;
|
||||
} else if (c == '{') this._state = "Ignore3";
|
||||
};
|
||||
CssTokenizer.prototype.Ignore3 = function(c) {
|
||||
if (c == '}') this._state = "Ignore2";
|
||||
};
|
||||
CssTokenizer.prototype.parse = function() {
|
||||
for (; this._index < this._buffer.length; this._index++)
|
||||
this[this._state](this._buffer[this._index]);
|
||||
return this.res;
|
||||
};
|
||||
module.exports = CssTokenizer;
|
||||
@@ -0,0 +1,325 @@
|
||||
//DomHandler.js
|
||||
const CssTokenizer = require('./CssTokenizer.js');
|
||||
const CanIUse = require('./api.js').versionHigherThan('2.7.1');
|
||||
const Common = 1,
|
||||
Rich = 2;
|
||||
const trustTag = {
|
||||
a: Rich,
|
||||
abbr: Common,
|
||||
audio: Rich,
|
||||
b: Common,
|
||||
blockquote: Common,
|
||||
br: Rich,
|
||||
code: Common,
|
||||
col: Rich,
|
||||
colgroup: Rich,
|
||||
dd: Common,
|
||||
del: Common,
|
||||
dl: Common,
|
||||
dt: Common,
|
||||
div: Common,
|
||||
em: Common,
|
||||
fieldset: Rich,
|
||||
font: Common,
|
||||
h1: Rich,
|
||||
h2: Rich,
|
||||
h3: Rich,
|
||||
h4: Rich,
|
||||
h5: Rich,
|
||||
h6: Rich,
|
||||
hr: Rich,
|
||||
i: Common,
|
||||
img: Common,
|
||||
ins: Common,
|
||||
label: Common,
|
||||
legend: Rich,
|
||||
li: Rich,
|
||||
ol: Rich,
|
||||
p: Common,
|
||||
q: Common,
|
||||
source: Rich,
|
||||
span: Common,
|
||||
strong: Common,
|
||||
sub: Rich,
|
||||
sup: Rich,
|
||||
table: Rich,
|
||||
tbody: Rich,
|
||||
td: Rich,
|
||||
tfoot: Rich,
|
||||
th: Rich,
|
||||
thead: Rich,
|
||||
tr: Rich,
|
||||
u: Common,
|
||||
ul: Rich,
|
||||
video: Common
|
||||
};
|
||||
const blockTag = {
|
||||
address: true,
|
||||
article: true,
|
||||
aside: true,
|
||||
body: true,
|
||||
center: true,
|
||||
cite: true,
|
||||
footer: true,
|
||||
header: true,
|
||||
html: true,
|
||||
nav: true,
|
||||
pre: true,
|
||||
section: true
|
||||
}
|
||||
const textTag = {
|
||||
a: true,
|
||||
abbr: true,
|
||||
b: true,
|
||||
big: true,
|
||||
code: true,
|
||||
del: true,
|
||||
em: true,
|
||||
font: true,
|
||||
i: true,
|
||||
ins: true,
|
||||
label: true,
|
||||
mark: true,
|
||||
q: true,
|
||||
s: true,
|
||||
small: true,
|
||||
span: true,
|
||||
strong: true,
|
||||
u: true
|
||||
};
|
||||
const ignoreTag = {
|
||||
area: true,
|
||||
base: true,
|
||||
basefont: true,
|
||||
canvas: true,
|
||||
circle: true,
|
||||
command: true,
|
||||
ellipse: true,
|
||||
embed: true,
|
||||
frame: true,
|
||||
head: true,
|
||||
iframe: true,
|
||||
input: true,
|
||||
isindex: true,
|
||||
keygen: true,
|
||||
line: true,
|
||||
link: true,
|
||||
map: true,
|
||||
meta: true,
|
||||
param: true,
|
||||
path: true,
|
||||
polygon: true,
|
||||
polyline: true,
|
||||
rect: true,
|
||||
script: true,
|
||||
stop: true,
|
||||
textarea: true,
|
||||
title: true,
|
||||
track: true,
|
||||
use: true,
|
||||
wbr: true
|
||||
};
|
||||
if (CanIUse) {
|
||||
trustTag.bdi = Rich;
|
||||
trustTag.bdo = Rich;
|
||||
trustTag.caption = Rich;
|
||||
trustTag.rt = Rich;
|
||||
trustTag.ruby = Rich;
|
||||
ignoreTag.rp = true;
|
||||
trustTag.big = Common;
|
||||
trustTag.small = Common;
|
||||
trustTag.pre = Rich;
|
||||
delete blockTag.pre;
|
||||
}
|
||||
//添加默认值
|
||||
function initStyle(tagStyle) {
|
||||
tagStyle.a = "display:inline;color:#366092;word-break:break-all;" + (tagStyle.a || "");
|
||||
tagStyle.address = "font-style:italic;" + (tagStyle.address || "");
|
||||
tagStyle.blockquote = tagStyle.blockquote || 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px';
|
||||
tagStyle.center = 'text-align:center;' + (tagStyle.center || "");
|
||||
tagStyle.cite = "font-style:italic;" + (tagStyle.cite || "");
|
||||
tagStyle.code = tagStyle.code || 'padding:0 1px 0 1px;margin-left:2px;margin-right:2px;background-color:#f8f8f8;border:1px solid #cccccc;border-radius:3px';
|
||||
tagStyle.dd = "margin-left:40px;" + (tagStyle.dd || "");
|
||||
tagStyle.img = "max-width:100%;" + (tagStyle.img || "");
|
||||
tagStyle.mark = "display:inline;background-color:yellow;" + (tagStyle.mark || "");
|
||||
tagStyle.pre = "overflow:scroll;" + (tagStyle.pre || 'background-color:#f6f8fa;padding:5px;border-radius:5px;');
|
||||
tagStyle.s = "display:inline;text-decoration:line-through;" + (tagStyle.s || "");
|
||||
tagStyle.u = "display:inline;text-decoration:underline;" + (tagStyle.u || "");
|
||||
//低版本兼容
|
||||
if (!CanIUse) {
|
||||
blockTag.caption = true;
|
||||
tagStyle.big = "display:inline;font-size:1.2em;" + (tagStyle.big || "");
|
||||
tagStyle.small = "display:inline;font-size:0.8em;" + (tagStyle.small || "");
|
||||
tagStyle.pre = "font-family:monospace;white-space:pre;" + tagStyle.pre;
|
||||
}
|
||||
return tagStyle;
|
||||
}
|
||||
|
||||
function DomHandler(style, tagStyle = {}) {
|
||||
this.imgList = [];
|
||||
this.nodes = [];
|
||||
this.title = "";
|
||||
this._videoNum = 0;
|
||||
this._audioNum = 0;
|
||||
this._style = new CssTokenizer(style, initStyle(tagStyle)).parse();
|
||||
this._tagStack = [];
|
||||
}
|
||||
DomHandler.prototype._addDomElement = function(element) {
|
||||
let parent = this._tagStack[this._tagStack.length - 1];
|
||||
let siblings = parent ? parent.children : this.nodes;
|
||||
siblings.push(element);
|
||||
};
|
||||
DomHandler.prototype._bubbling = function() {
|
||||
for (let i = this._tagStack.length - 1; i >= 0; i--) {
|
||||
if (trustTag[this._tagStack[i].name] == Common) this._tagStack[i].continue = true;
|
||||
else return this._tagStack[i].name;
|
||||
}
|
||||
}
|
||||
DomHandler.prototype.onopentag = function(name, attrs) {
|
||||
let element = {
|
||||
children: []
|
||||
};
|
||||
//匹配样式
|
||||
let matched = this._style[name] ? (this._style[name] + ';') : '';
|
||||
if (attrs.id) {
|
||||
matched += (this._style['#' + attrs.id] ? (this._style['#' + attrs.id] + ';') : '');
|
||||
delete attrs.id;
|
||||
}
|
||||
if (attrs.class) {
|
||||
for (var Class of attrs.class.split(' ')) {
|
||||
matched += (this._style['.' + Class] ? (this._style['.' + Class] + ';') : '');
|
||||
}
|
||||
delete attrs.class;
|
||||
}
|
||||
//处理属性
|
||||
switch (name) {
|
||||
case 'div':
|
||||
case 'p':
|
||||
if (attrs.align) {
|
||||
attrs.style += (';text-align:' + attrs.align);
|
||||
delete attrs.align;
|
||||
}
|
||||
break;
|
||||
case 'img':
|
||||
if (attrs.width) {
|
||||
attrs.style = 'width:' + attrs.width + (/[0-9]/.test(attrs.width[attrs.width.length - 1]) ? 'px' : '') + ';' + attrs.style;
|
||||
delete attrs.width;
|
||||
}
|
||||
if (attrs['data-src']) {
|
||||
attrs.src = attrs.src || attrs['data-src'];
|
||||
delete attrs['data-src'];
|
||||
}
|
||||
if (!attrs.hasOwnProperty('ignore') && attrs.src) {
|
||||
this.imgList.push(attrs.src);
|
||||
if (this._bubbling() == 'a') attrs.ignore = "";
|
||||
};
|
||||
break;
|
||||
case 'font':
|
||||
name = 'span';
|
||||
if (attrs.color) {
|
||||
attrs.style += (';color:' + attrs.color);
|
||||
delete attrs.color;
|
||||
}
|
||||
if (attrs.face) {
|
||||
attrs.style += (";font-family:" + attrs.face);
|
||||
delete attrs.face;
|
||||
}
|
||||
if (attrs.size) {
|
||||
var size = parseInt(attrs.size);
|
||||
if (size < 1) size = 1;
|
||||
else if (size > 7) size = 7;
|
||||
switch (size) {
|
||||
case 1:
|
||||
size = 10;
|
||||
break;
|
||||
case 2:
|
||||
size = 13;
|
||||
break;
|
||||
case 3:
|
||||
size = 16;
|
||||
break;
|
||||
case 4:
|
||||
size = 18;
|
||||
break;
|
||||
case 5:
|
||||
size = 24;
|
||||
break;
|
||||
case 6:
|
||||
size = 32;
|
||||
break;
|
||||
case 7:
|
||||
size = 48;
|
||||
break;
|
||||
}
|
||||
attrs.style += (";font-size:" + size + "px");
|
||||
delete attrs.size;
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
this._bubbling();
|
||||
break;
|
||||
case 'video':
|
||||
case 'audio':
|
||||
attrs.loop = attrs.hasOwnProperty('loop');
|
||||
attrs.controls = attrs.hasOwnProperty('controls');
|
||||
attrs.autoplay = attrs.hasOwnProperty('autoplay');
|
||||
if (name == 'video') {
|
||||
attrs.muted = attrs.hasOwnProperty('muted');
|
||||
if (attrs.width) {
|
||||
attrs.style = 'width:' + parseFloat(attrs.width) + 'px;' + attrs.style;
|
||||
delete attrs.width;
|
||||
}
|
||||
if (attrs.height) {
|
||||
attrs.style = 'height:' + parseFloat(attrs.height) + 'px;' + attrs.style;
|
||||
delete attrs.height;
|
||||
}
|
||||
}
|
||||
attrs.id = (name + (++this['_' + name + 'Num']));
|
||||
attrs.source = [];
|
||||
if (attrs.src) attrs.source.push(attrs.src);
|
||||
if (!attrs.controls && !attrs.autoplay)
|
||||
console.warn('存在没有controls属性的' + name + '标签,可能导致无法播放', attrs);
|
||||
this._bubbling();
|
||||
break;
|
||||
case 'source':
|
||||
let parent = this._tagStack[this._tagStack.length - 1];
|
||||
if (parent && (parent.name == 'video' || parent.name == 'audio')) {
|
||||
parent.attrs.source.push(attrs.src);
|
||||
if (!parent.attrs.src) parent.attrs.src = attrs.src;
|
||||
}
|
||||
this._tagStack.push(element);
|
||||
return;
|
||||
}
|
||||
attrs.style = matched + attrs.style;
|
||||
if (textTag[name]) element.continue = true;
|
||||
else if (blockTag[name]) name = 'div';
|
||||
else if (!trustTag[name]) name = 'span';
|
||||
element.name = name;
|
||||
element.attrs = attrs;
|
||||
this._addDomElement(element);
|
||||
this._tagStack.push(element);
|
||||
};
|
||||
DomHandler.prototype.ontext = function(data) {
|
||||
if (/\S/.test(data)) {
|
||||
let element = {
|
||||
text: data.replace(/ /g, '\u00A0'),
|
||||
type: 'text'
|
||||
};
|
||||
if (/&#*((?!sp|lt|gt).){2,5};/.test(data)) element.decode = true;
|
||||
this._addDomElement(element);
|
||||
}
|
||||
};
|
||||
DomHandler.prototype.onclosetag = function(name) {
|
||||
let element = this._tagStack.pop();
|
||||
if (ignoreTag[name]) {
|
||||
if (name == 'title') {
|
||||
try {
|
||||
this.title = element.children[0].text;
|
||||
} catch (e) {}
|
||||
}
|
||||
let parent = this._tagStack[this._tagStack.length - 1];
|
||||
let siblings = parent ? parent.children : this.nodes;
|
||||
siblings.pop();
|
||||
}
|
||||
};
|
||||
module.exports = DomHandler;
|
||||
@@ -0,0 +1,156 @@
|
||||
//Parser.js
|
||||
const Tokenizer = require("./Tokenizer.js");
|
||||
const DomHandler = require("./DomHandler.js");
|
||||
const trustAttrs = {
|
||||
align: true,
|
||||
alt: true,
|
||||
author: true,
|
||||
autoplay: true,
|
||||
class: true,
|
||||
color: true,
|
||||
colspan: true,
|
||||
controls: true,
|
||||
"data-src": true,
|
||||
dir: true,
|
||||
face: true,
|
||||
height: true,
|
||||
href: true,
|
||||
id: true,
|
||||
ignore: true,
|
||||
loop: true,
|
||||
muted: true,
|
||||
name: true,
|
||||
poster: true,
|
||||
rowspan: true,
|
||||
size: true,
|
||||
span: true,
|
||||
src: true,
|
||||
start: true,
|
||||
style: true,
|
||||
type: true,
|
||||
width: true,
|
||||
};
|
||||
const voidTag = {
|
||||
area: true,
|
||||
base: true,
|
||||
basefont: true,
|
||||
br: true,
|
||||
col: true,
|
||||
circle: true,
|
||||
command: true,
|
||||
ellipse: true,
|
||||
embed: true,
|
||||
frame: true,
|
||||
hr: true,
|
||||
img: true,
|
||||
input: true,
|
||||
isindex: true,
|
||||
keygen: true,
|
||||
line: true,
|
||||
link: true,
|
||||
meta: true,
|
||||
param: true,
|
||||
path: true,
|
||||
polygon: true,
|
||||
polyline: true,
|
||||
rect: true,
|
||||
source: true,
|
||||
stop: true,
|
||||
track: true,
|
||||
use: true,
|
||||
wbr: true
|
||||
};
|
||||
|
||||
function Parser(cbs, callback) {
|
||||
this._cbs = cbs;
|
||||
this._callback = callback;
|
||||
this._tagname = "";
|
||||
this._attribname = "";
|
||||
this._attribvalue = "";
|
||||
this._attribs = null;
|
||||
this._stack = [];
|
||||
this._tokenizer = new Tokenizer(this);
|
||||
}
|
||||
Parser.prototype.ontext = function(data) {
|
||||
this._cbs.ontext(data);
|
||||
};
|
||||
Parser.prototype.onopentagname = function(name) {
|
||||
name = name.toLowerCase();
|
||||
this._tagname = name;
|
||||
this._attribs = {
|
||||
style: ''
|
||||
};
|
||||
if (!voidTag[name]) this._stack.push(name);
|
||||
};
|
||||
Parser.prototype.onopentagend = function() {
|
||||
if (this._attribs) {
|
||||
this._cbs.onopentag(this._tagname, this._attribs);
|
||||
this._attribs = null;
|
||||
}
|
||||
if (voidTag[this._tagname]) this._cbs.onclosetag(this._tagname);
|
||||
this._tagname = "";
|
||||
};
|
||||
Parser.prototype.onclosetag = function(name) {
|
||||
name = name.toLowerCase();
|
||||
if (this._stack.length && !voidTag[name]) {
|
||||
var pos = this._stack.lastIndexOf(name);
|
||||
if (pos !== -1) {
|
||||
pos = this._stack.length - pos;
|
||||
while (pos--) this._cbs.onclosetag(this._stack.pop());
|
||||
} else if (name === "p") {
|
||||
this.onopentagname(name);
|
||||
this._closeCurrentTag();
|
||||
}
|
||||
} else if (name === "br" || name === "hr" || name === "p") {
|
||||
this.onopentagname(name);
|
||||
this._closeCurrentTag();
|
||||
}
|
||||
};
|
||||
Parser.prototype._closeCurrentTag = function() {
|
||||
let name = this._tagname;
|
||||
this.onopentagend();
|
||||
if (this._stack[this._stack.length - 1] === name) {
|
||||
this._cbs.onclosetag(name);
|
||||
this._stack.pop();
|
||||
}
|
||||
};
|
||||
Parser.prototype.onattribend = function() {
|
||||
this._attribvalue = this._attribvalue.replace(/"/g, '"');
|
||||
if (this._attribs && trustAttrs[this._attribname]) {
|
||||
this._attribs[this._attribname] = this._attribvalue;
|
||||
}
|
||||
this._attribname = "";
|
||||
this._attribvalue = "";
|
||||
};
|
||||
Parser.prototype.onend = function() {
|
||||
for (
|
||||
var i = this._stack.length; i > 0; this._cbs.onclosetag(this._stack[--i])
|
||||
);
|
||||
this._callback({
|
||||
'nodes': this._cbs.nodes,
|
||||
'title': this._cbs.title,
|
||||
'imgList': this._cbs.imgList
|
||||
});
|
||||
};
|
||||
Parser.prototype.write = function(chunk) {
|
||||
this._tokenizer.parse(chunk);
|
||||
};
|
||||
|
||||
function html2nodes(data, tagStyle) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
try {
|
||||
let style = '';
|
||||
data = data.replace(/<style.*?>([\s\S]*?)<\/style>/gi, function() {
|
||||
style += arguments[1];
|
||||
return '';
|
||||
});
|
||||
let handler = new DomHandler(style, tagStyle);
|
||||
new Parser(handler, (res) => {
|
||||
return resolve(res);
|
||||
}).write(data);
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
module.exports = html2nodes;
|
||||
@@ -0,0 +1,218 @@
|
||||
//Tokenizer.js
|
||||
function Tokenizer(cbs) {
|
||||
this._state = "TEXT";
|
||||
this._buffer = "";
|
||||
this._sectionStart = 0;
|
||||
this._index = 0;
|
||||
this._cbs = cbs;
|
||||
}
|
||||
Tokenizer.prototype.TEXT = function(c) {
|
||||
var index = this._buffer.indexOf("<", this._index);
|
||||
if (index != -1) {
|
||||
this._index = index;
|
||||
this._cbs.ontext(this._getSection());
|
||||
this._state = "BeforeTag";
|
||||
this._sectionStart = this._index;
|
||||
} else this._index = this._buffer.length;
|
||||
};
|
||||
Tokenizer.prototype.BeforeTag = function(c) {
|
||||
switch (c) {
|
||||
case "/":
|
||||
this._state = "BeforeCloseTag";
|
||||
break;
|
||||
case "!":
|
||||
this._state = "BeforeDeclaration";
|
||||
break;
|
||||
case "?":
|
||||
let index = this._buffer.indexOf(">", this._index);
|
||||
if (index != -1) {
|
||||
this._index = index;
|
||||
this._sectionStart = this._index + 1;
|
||||
} else this._sectionStart = this._index = this._buffer.length;
|
||||
this._state = "TEXT";
|
||||
break;
|
||||
case ">":
|
||||
this._state = "TEXT";
|
||||
break;
|
||||
case "<":
|
||||
this._cbs.ontext(this._getSection());
|
||||
this._sectionStart = this._index;
|
||||
break;
|
||||
default:
|
||||
if (/\s/.test(c)) this._state = "TEXT";
|
||||
else {
|
||||
this._state = "InTag";
|
||||
this._sectionStart = this._index;
|
||||
}
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.InTag = function(c) {
|
||||
if (c === "/" || c === ">" || /\s/.test(c)) {
|
||||
this._cbs.onopentagname(this._getSection());
|
||||
this._state = "BeforeAttrsName";
|
||||
this._index--;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.BeforeAttrsName = function(c) {
|
||||
if (c === ">") {
|
||||
this._cbs.onopentagend();
|
||||
this._state = "TEXT";
|
||||
this._sectionStart = this._index + 1;
|
||||
} else if (c === "/") {
|
||||
this._state = "InSelfCloseTag";
|
||||
} else if (!(/\s/.test(c))) {
|
||||
this._state = "InAttrsName";
|
||||
this._sectionStart = this._index;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.InAttrsName = function(c) {
|
||||
if (c === "=" || c === "/" || c === ">" || /\s/.test(c)) {
|
||||
this._cbs._attribname = this._getSection().toLowerCase();
|
||||
this._sectionStart = -1;
|
||||
this._state = "AfterAttrsName";
|
||||
this._index--;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.AfterAttrsName = function(c) {
|
||||
if (c === "=") {
|
||||
this._state = "BeforeAttrsValue";
|
||||
} else if (c === "/" || c === ">") {
|
||||
this._cbs.onattribend();
|
||||
this._state = "BeforeAttrsName";
|
||||
this._index--;
|
||||
} else if (!(/\s/.test(c))) {
|
||||
this._cbs.onattribend();
|
||||
this._state = "InAttrsName";
|
||||
this._sectionStart = this._index;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.BeforeAttrsValue = function(c) {
|
||||
if (c === '"') {
|
||||
this._state = "InAttrsValueDQ";
|
||||
this._sectionStart = this._index + 1;
|
||||
} else if (c === "'") {
|
||||
this._state = "InAttrsValueSQ";
|
||||
this._sectionStart = this._index + 1;
|
||||
} else if (!(/\s/.test(c))) {
|
||||
this._state = "InAttrsValueNQ";
|
||||
this._sectionStart = this._index;
|
||||
this._index--;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.InAttrsValueDQ = function(c) {
|
||||
if (c === '"') {
|
||||
this._cbs._attribvalue += this._getSection();
|
||||
this._cbs.onattribend();
|
||||
this._state = "BeforeAttrsName";
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.InAttrsValueSQ = function(c) {
|
||||
if (c === "'") {
|
||||
this._cbs._attribvalue += this._getSection();
|
||||
this._cbs.onattribend();
|
||||
this._state = "BeforeAttrsName";
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.InAttrsValueNQ = function(c) {
|
||||
if (/\s/.test(c) || c === ">") {
|
||||
this._cbs._attribvalue += this._getSection();
|
||||
this._cbs.onattribend();
|
||||
this._state = "BeforeAttrsName";
|
||||
this._index--;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.BeforeCloseTag = function(c) {
|
||||
if (/\s/.test(c));
|
||||
else if (c === ">") {
|
||||
this._state = "TEXT";
|
||||
} else {
|
||||
this._state = "InCloseTag";
|
||||
this._sectionStart = this._index;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.InCloseTag = function(c) {
|
||||
if (c === ">" || /\s/.test(c)) {
|
||||
this._cbs.onclosetag(this._getSection());
|
||||
this._state = "AfterCloseTag";
|
||||
this._index--;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.InSelfCloseTag = function(c) {
|
||||
if (c === ">") {
|
||||
this._cbs.onopentagend();
|
||||
this._state = "TEXT";
|
||||
this._sectionStart = this._index + 1;
|
||||
} else if (!(/\s/.test(c))) {
|
||||
this._state = "BeforeAttrsName";
|
||||
this._index--;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.AfterCloseTag = function(c) {
|
||||
if (c === ">") {
|
||||
this._state = "TEXT";
|
||||
this._sectionStart = this._index + 1;
|
||||
}
|
||||
};
|
||||
Tokenizer.prototype.BeforeDeclaration = function(c) {
|
||||
if (c == '-') this._state = "InComment";
|
||||
else if (c == '[') this._state = "BeforeCDATA1";
|
||||
else this._state = "InDeclaration";
|
||||
};
|
||||
Tokenizer.prototype.InDeclaration = function(c) {
|
||||
var index = this._buffer.indexOf(">", this._index);
|
||||
if (index != -1) {
|
||||
this._index = index;
|
||||
this._sectionStart = index + 1;
|
||||
} else this._sectionStart = this._index = this._buffer.length;
|
||||
this._state = "TEXT";
|
||||
};
|
||||
Tokenizer.prototype.InComment = function(c) {
|
||||
let key = (c == '-' ? '-->' : '>');
|
||||
let index = this._buffer.indexOf(key, this._index);
|
||||
if (index != -1) {
|
||||
this._index = index + key.length - 1;
|
||||
this._sectionStart = this._index + 1;
|
||||
} else this._sectionStart = this._index = this._buffer.length;
|
||||
this._state = "TEXT";
|
||||
};
|
||||
Tokenizer.prototype.BeforeCDATA1 = function(c) {
|
||||
if (c == 'C') this._state = "BeforeCDATA2";
|
||||
else this._state = "InDeclaration";
|
||||
};
|
||||
Tokenizer.prototype.BeforeCDATA2 = function(c) {
|
||||
if (c == 'D') this._state = "BeforeCDATA3";
|
||||
else this._state = "InDeclaration";
|
||||
};
|
||||
Tokenizer.prototype.BeforeCDATA3 = function(c) {
|
||||
if (c == 'A') this._state = "BeforeCDATA4";
|
||||
else this._state = "InDeclaration";
|
||||
};
|
||||
Tokenizer.prototype.BeforeCDATA4 = function(c) {
|
||||
if (c == 'T') this._state = "BeforeCDATA5";
|
||||
else this._state = "InDeclaration";
|
||||
};
|
||||
Tokenizer.prototype.BeforeCDATA5 = function(c) {
|
||||
if (c == 'A') this._state = "InCDATA";
|
||||
else this._state = "InDeclaration";
|
||||
};
|
||||
Tokenizer.prototype.InCDATA = function(c) {
|
||||
let key = (c == '[' ? ']]>' : '>');
|
||||
let index = this._buffer.indexOf(key, this._index);
|
||||
if (index != -1) {
|
||||
this._index = index + key.length - 1;
|
||||
this._sectionStart = this._index + 1;
|
||||
} else this._sectionStart = this._index = this._buffer.length;
|
||||
this._state = "TEXT";
|
||||
};
|
||||
Tokenizer.prototype.parse = function(chunk) {
|
||||
this._buffer += chunk;
|
||||
for (; this._index < this._buffer.length; this._index++)
|
||||
this[this._state](this._buffer[this._index]);
|
||||
if (this._state === "TEXT" && this._sectionStart !== this._index)
|
||||
this._cbs.ontext(this._buffer.substr(this._sectionStart));
|
||||
this._cbs.onend();
|
||||
};
|
||||
Tokenizer.prototype._getSection = function() {
|
||||
return this._buffer.substring(this._sectionStart, this._index);
|
||||
};
|
||||
module.exports = Tokenizer;
|
||||
@@ -0,0 +1,36 @@
|
||||
String.prototype.splice = function(start = 0, deleteCount = 0, addStr = '') {
|
||||
if (start < 0) start = this.length + start;
|
||||
if (deleteCount < 0) deleteCount = 0;
|
||||
return this.substring(0, start) + addStr + this.substring(start + deleteCount);
|
||||
}
|
||||
module.exports = {
|
||||
versionHigherThan(version = '') {
|
||||
var v1 = wx.getSystemInfoSync().SDKVersion.split('.');
|
||||
var v2 = version.split('.');
|
||||
const len = Math.max(v1.length, v2.length);
|
||||
while (v1.length < len) {
|
||||
v1.push('0');
|
||||
}
|
||||
while (v2.length < len) {
|
||||
v2.push('0');
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1[i]);
|
||||
const num2 = parseInt(v2[i]);
|
||||
if (num1 > num2) {
|
||||
return true;
|
||||
} else if (num1 < num2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
html2nodes(html, tagStyle) {
|
||||
const Parser = require('./Parser.js');
|
||||
return Parser(html, tagStyle);
|
||||
},
|
||||
css2object(style, tagStyle) {
|
||||
const CssTokenizer = require('./CssTokenizer.js');
|
||||
return new CssTokenizer(style, tagStyle).parse();
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
//Parser组件
|
||||
const html2nodes = require('./Parser.js');
|
||||
const initData = function(Component) {
|
||||
setTimeout(() => {
|
||||
Component.createSelectorQuery().select('#contain').boundingClientRect(res => {
|
||||
Component.triggerEvent('ready', res);
|
||||
}).exec();
|
||||
Component.videoContext = [];
|
||||
let nodes = [Component.selectComponent('#contain')];
|
||||
nodes = nodes.concat(Component.selectAllComponents('#contain>>>#node'));
|
||||
for (let node of nodes) {
|
||||
for (let item of node.data.nodes) {
|
||||
if (item.name == 'video') {
|
||||
Component.videoContext.push({
|
||||
id: item.attrs.id,
|
||||
context: wx.createVideoContext(item.attrs.id, node)
|
||||
});
|
||||
} else if (item.name == 'audio' && item.attrs.autoplay)
|
||||
wx.createAudioContext(item.attrs.id, node).play();
|
||||
}
|
||||
}
|
||||
}, 10)
|
||||
}
|
||||
Component({
|
||||
properties: {
|
||||
'html': {
|
||||
type: String,
|
||||
value: '',
|
||||
observer: function(html) {
|
||||
let hideAnimation = {},
|
||||
showAnimation = {};
|
||||
if (this.data.showWithAnimation) {
|
||||
hideAnimation = wx.createAnimation({
|
||||
duration: this.data.animationDuration,
|
||||
timingFunction: "ease"
|
||||
}).opacity(0).step().export();
|
||||
showAnimation = wx.createAnimation({
|
||||
duration: this.data.animationDuration,
|
||||
timingFunction: "ease"
|
||||
}).opacity(1).step().export();
|
||||
}
|
||||
if (!html) {
|
||||
this.setData({
|
||||
nodes: []
|
||||
})
|
||||
} else if (typeof html == 'string') {
|
||||
html2nodes(html, this.data.tagStyle).then(res => {
|
||||
this.setData({
|
||||
nodes: res.nodes,
|
||||
controls: {
|
||||
imgMode: this.data.imgMode
|
||||
},
|
||||
showAnimation,
|
||||
hideAnimation
|
||||
}, initData(this))
|
||||
if (res.title && this.data.autosetTitle) {
|
||||
wx.setNavigationBarTitle({
|
||||
title: res.title
|
||||
})
|
||||
}
|
||||
this.imgList = res.imgList;
|
||||
this.triggerEvent('parse', res);
|
||||
}).catch(err => {
|
||||
this.triggerEvent('error', err);
|
||||
})
|
||||
} else if (html.constructor == Array) {
|
||||
this.setData({
|
||||
controls: {
|
||||
imgMode: this.data.imgMode
|
||||
},
|
||||
showAnimation,
|
||||
hideAnimation
|
||||
}, initData(this))
|
||||
this.imgList = [];
|
||||
} else if (typeof html == 'object') {
|
||||
if (!html.nodes || html.nodes.constructor != Array) {
|
||||
this.triggerEvent('error', {
|
||||
message: "传入的nodes数组格式不正确!应该传入的类型是array,实际传入的类型是:" + typeof html.nodes
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setData({
|
||||
controls: {
|
||||
imgMode: this.data.imgMode
|
||||
},
|
||||
showAnimation,
|
||||
hideAnimation
|
||||
}, initData(this))
|
||||
if (html.title && this.data.autosetTitle) {
|
||||
wx.setNavigationBarTitle({
|
||||
title: html.title
|
||||
})
|
||||
}
|
||||
this.imgList = html.imgList || [];
|
||||
} else {
|
||||
this.triggerEvent('error', {
|
||||
message: "错误的html类型:" + typeof html
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
'autocopy': {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
'autopause': {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
'autosetTitle':{
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
'imgMode': {
|
||||
type: String,
|
||||
value: "default"
|
||||
},
|
||||
'selectable': {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
'tagStyle': {
|
||||
type: Object,
|
||||
value: { video:"width:100%" }
|
||||
},
|
||||
'showWithAnimation': {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
'animationDuration': {
|
||||
type: Number,
|
||||
value: 400
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//事件
|
||||
tapEvent(e) {
|
||||
if (this.data.autocopy && e.detail && /^http/.test(e.detail)) {
|
||||
wx.setClipboardData({
|
||||
data: e.detail,
|
||||
success() {
|
||||
wx.showToast({
|
||||
title: '链接已复制',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
this.triggerEvent('linkpress', e.detail);
|
||||
},
|
||||
errorEvent(e) {
|
||||
this.triggerEvent('error', e.detail);
|
||||
},
|
||||
//内部方法
|
||||
_previewImg(e) {
|
||||
wx.previewImage({
|
||||
current: e.detail,
|
||||
urls: this.imgList.length ? this.imgList : [e.detail],
|
||||
})
|
||||
},
|
||||
_playVideo(e) {
|
||||
if (this.videoContext.length > 1 && this.data.autopause) {
|
||||
for (let video of this.videoContext) {
|
||||
if (video.id == e.detail) continue;
|
||||
video.context.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"trees": "./trees/trees"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<!--Parser-->
|
||||
<slot wx:if="{{!(html.nodes||((html[0].name||html[0].type)?1:nodes.length))}}" animation="{{hideAnimation}}"></slot>
|
||||
<trees id="contain" style="{{(showWithAnimation?'opacity:0;':'')+(selectable?'user-select:text;-webkit-user-select:text':'')}}" animation="{{showAnimation}}" nodes="{{html.nodes||((html[0].name||html[0].type)?html:nodes)}}" controls="{{controls}}" catchpreview="_previewImg"
|
||||
catchplay="_playVideo" catchlinkpress="tapEvent" catcherror="errorEvent" />
|
||||
@@ -0,0 +1,5 @@
|
||||
:host {
|
||||
display: block;
|
||||
overflow: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Parser/trees/cssHandler.wxs
|
||||
module.exports = {
|
||||
getStyle: function(style, display) {
|
||||
var res = "";
|
||||
var reg = getRegExp("float\s*:\s*[^;]*", "i");
|
||||
if (reg.test(style)) res += reg.exec(style)[0];
|
||||
reg = getRegExp("margin[^;]*auto", "i");
|
||||
if (reg.test(style)) res += (";" + reg.exec(style)[0]);
|
||||
reg = getRegExp("display\s*:\s*([^;]*)", "i");
|
||||
if (reg.test(style) && reg.exec(style)[1]!="flex") res += (';' + reg.exec(style)[0]);
|
||||
else res += (';display:' + display);
|
||||
reg = getRegExp("[^;\s]*width\s*:\s*[^;]*", "ig");
|
||||
var width = reg.exec(style);
|
||||
while (width) {
|
||||
res += (';' + width[0]);
|
||||
width = reg.exec(style);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
setImgStyle: function(item, imgMode) {
|
||||
if (getRegExp("[^-]width\s*:\s*[^;]*", "i").test(';' + item.attrs.style))
|
||||
item.attrs.style += ';width:100%';
|
||||
if (imgMode == "widthFix")
|
||||
item.attrs.style += ";height:auto !important";
|
||||
return [item];
|
||||
},
|
||||
setStyle: function(item) {
|
||||
if (getRegExp("[^-]width\s*:\s*[^;]*", "i").test(';' + item.attrs.style))
|
||||
item.attrs.style += ';width:100%';
|
||||
return [item];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Parser/trees/trees.js
|
||||
Component({
|
||||
properties: {
|
||||
nodes: {
|
||||
type: Array,
|
||||
value: []
|
||||
},
|
||||
controls: {
|
||||
type: Object,
|
||||
value: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//冒泡事件
|
||||
playEvent(e) {
|
||||
this.triggerEvent('play', e.currentTarget.dataset.id, {
|
||||
bubbles: true,
|
||||
composed: true
|
||||
});
|
||||
},
|
||||
previewEvent(e) {
|
||||
if (!e.target.dataset.hasOwnProperty('ignore')) {
|
||||
this.triggerEvent('preview', e.currentTarget.dataset.src, {
|
||||
bubbles: true,
|
||||
composed: true
|
||||
});
|
||||
}
|
||||
},
|
||||
tapEvent(e) {
|
||||
this.triggerEvent('linkpress', e.currentTarget.dataset.href, {
|
||||
bubbles: true,
|
||||
composed: true
|
||||
});
|
||||
},
|
||||
errorEvent(e) {
|
||||
//尝试加载其他源
|
||||
if (!this.data.controls[e.currentTarget.dataset.id] && e.currentTarget.dataset.source.length > 1) {
|
||||
this.data.controls[e.currentTarget.dataset.id] = {
|
||||
play: false,
|
||||
index: 1
|
||||
}
|
||||
} else if (this.data.controls[e.currentTarget.dataset.id] && e.currentTarget.dataset.source.length > (this.data.controls[e.currentTarget.dataset.id].index + 1)) {
|
||||
this.data.controls[e.currentTarget.dataset.id].index++;
|
||||
}
|
||||
this.setData({
|
||||
controls: this.data.controls
|
||||
})
|
||||
this.triggerEvent('error', {
|
||||
target: e.currentTarget,
|
||||
message: e.detail.errMsg
|
||||
}, {
|
||||
bubbles: true,
|
||||
composed: true
|
||||
});
|
||||
},
|
||||
//内部方法:加载视频
|
||||
_loadVideo(e) {
|
||||
this.data.controls[e.currentTarget.dataset.id] = {
|
||||
play: true,
|
||||
index: 0
|
||||
}
|
||||
this.setData({
|
||||
controls: this.data.controls
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"trees":"./trees"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<!--Parser/trees/trees.wxml-->
|
||||
<wxs module="cssHandler" src="./cssHandler.wxs" />
|
||||
<block wx:for='{{nodes}}' wx:key="index">
|
||||
<block wx:if="{{!item.continue}}">
|
||||
<!--图片-->
|
||||
<rich-text wx:if="{{item.name=='img'}}" style="{{cssHandler.getStyle(item.attrs.style,'inline-block')}}" nodes='{{cssHandler.setImgStyle(item,controls.imgMode)}}' data-ignore='{{item.attrs.ignore}}' data-src='{{item.attrs.src}}' bindtap='previewEvent' />
|
||||
<!--文本-->
|
||||
<block wx:elif="{{item.type=='text'}}">
|
||||
<text wx:if="{{!item.decode}}" decode>{{item.text}}</text>
|
||||
<rich-text wx:else style="display:inline-block" nodes="{{[item]}}"></rich-text>
|
||||
</block>
|
||||
<text wx:elif="{{item.name=='br'}}">\n</text>
|
||||
<!--视频-->
|
||||
<block wx:elif="{{item.name=='video'}}">
|
||||
<view wx:if="{{item.attrs.id>'video3'&&!controls[item.attrs.id].play}}" class="video" style="{{item.attrs.style+(item.attrs.width?(';width:'+item.attrs.width):'')+(item.attrs.height?('height:'+item.attrs.height):'')}}" data-id="{{item.attrs.id}}" bindtap="_loadVideo">
|
||||
<view class="video-triangle" />
|
||||
</view>
|
||||
<video wx:else src='{{controls[item.attrs.id]?item.attrs.source[controls[item.attrs.id].index]:item.attrs.src}}' id="{{item.attrs.id}}" loop='{{item.attrs.loop}}' controls='{{item.attrs.controls}}' autoplay="{{item.attrs.autoplay||controls[item.attrs.id].play}}"
|
||||
class="v" muted="{{item.attrs.muted}}" style="{{item.attrs.style+(item.attrs.width?(';width:'+item.attrs.width):'')+(item.attrs.height?('height:'+item.attrs.height):'')}}" data-id="{{item.attrs.id}}" data-source="{{item.attrs.source}}" bindplay='playEvent'
|
||||
binderror="errorEvent" />
|
||||
</block>
|
||||
<!--音频-->
|
||||
<audio wx:elif="{{item.name=='audio'}}" src='{{controls[item.attrs.id]?item.attrs.source[controls[item.attrs.id].index]:item.attrs.src}}' id="{{item.attrs.id}}" loop='{{item.attrs.loop}}' controls='{{item.attrs.controls}}' poster='{{item.attrs.poster}}'
|
||||
name='{{item.attrs.name}}' author='{{item.attrs.author}}' style="{{item.attrs.style}}" data-id="{{item.attrs.id}}" data-source="{{item.attrs.source}}" binderror="errorEvent" />
|
||||
<!--富文本-->
|
||||
<rich-text wx:else class="{{item.name}}" style="{{cssHandler.getStyle(item.attrs.style,'block')}}" nodes="{{cssHandler.setStyle(item)}}" />
|
||||
</block>
|
||||
<!--链接-->
|
||||
<navigator wx:elif="{{item.name=='a'}}" url="{{item.attrs.href}}" style="{{item.attrs.style}}" data-href='{{item.attrs.href}}' hover-class="navigator-hover" hover-start-time="25" hover-stay-time="300" bindtap="tapEvent">
|
||||
<trees id="node" nodes="{{item.children}}" controls="{{controls}}" />
|
||||
</navigator>
|
||||
<!--继续递归-->
|
||||
<trees wx:else id="node" class="{{item.name}}" style="{{item.attrs.style}}" nodes="{{item.children}}" controls="{{controls}}" />
|
||||
</block>
|
||||
@@ -0,0 +1,69 @@
|
||||
/* 链接受到点击的hover-class,可自定义修改 */
|
||||
.navigator-hover {
|
||||
opacity: 0.7;
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* 以下内容不建议修改 */
|
||||
:host {
|
||||
display: inherit;
|
||||
float: inherit;
|
||||
}
|
||||
.sub, .sup, .bdo, .bdi, .ruby, .rt {
|
||||
display: inline-block !important;
|
||||
}
|
||||
.div, .blockquote, .p{
|
||||
display:block;
|
||||
}
|
||||
.b, .strong {
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
.em, .i {
|
||||
display: inline;
|
||||
font-style: italic;
|
||||
}
|
||||
.del {
|
||||
display: inline;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.ins {
|
||||
display: inline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.code {
|
||||
display: inline;
|
||||
font-family: monospace;
|
||||
}
|
||||
.big {
|
||||
font-size:1.2em;
|
||||
display:inline;
|
||||
}
|
||||
.small {
|
||||
font-size:0.8em;
|
||||
display:inline;
|
||||
}
|
||||
.q, .span, .label, .abbr {
|
||||
display: inline;
|
||||
}
|
||||
.q::before {
|
||||
content: '"';
|
||||
}
|
||||
.q::after {
|
||||
content: '"';
|
||||
}
|
||||
.video {
|
||||
background-color: rgb(0, 0, 0);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.video-triangle {
|
||||
border-width: 15px 0 15px 30px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent transparent white;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin: -15px 0 0 -15px;
|
||||
}
|
||||
Reference in New Issue
Block a user