mirror of
https://github.com/StepanovPlaton/SSAUGenerator.git
synced 2026-10-31 18:28:29 +04:00
Fix DOCX headings, captions and page breaks; polish PPTX layout
- docx: zero paragraph indent for centered headings/captions/figure notes so they no longer shift right (empty <w:ind/> inherited 709 twips) - docx: use pageBreakBefore instead of a separate break paragraph; drop the merger page break to remove the blank page after the title page - docx: heading numbers separated by space instead of tab - docx: bullet marker is the Symbol dash U+F02D - pptx: narrower slide number moved left and bold; content title lower; section title on the right half like title/final
This commit is contained in:
@@ -33,6 +33,10 @@ async function buildDocument(config, { check = false } = {}) {
|
|||||||
const ctx = buildContext(config);
|
const ctx = buildContext(config);
|
||||||
const state = createState(md, sections);
|
const state = createState(md, sections);
|
||||||
|
|
||||||
|
const titlePage = config.document.titlePage;
|
||||||
|
const hasTitlePage = Boolean(titlePage && fs.existsSync(titlePage));
|
||||||
|
ctx.hasTitlePage = hasTitlePage;
|
||||||
|
|
||||||
collectNumbers(sections, ctx, state);
|
collectNumbers(sections, ctx, state);
|
||||||
await resolvePlantUml(ctx, sections, state);
|
await resolvePlantUml(ctx, sections, state);
|
||||||
const children = renderSections(ctx, state);
|
const children = renderSections(ctx, state);
|
||||||
@@ -42,8 +46,7 @@ async function buildDocument(config, { check = false } = {}) {
|
|||||||
const doc = buildDocx(ctx, children);
|
const doc = buildDocx(ctx, children);
|
||||||
let buffer = await Packer.toBuffer(doc);
|
let buffer = await Packer.toBuffer(doc);
|
||||||
|
|
||||||
const titlePage = config.document.titlePage;
|
if (hasTitlePage) {
|
||||||
if (titlePage && fs.existsSync(titlePage)) {
|
|
||||||
buffer = await mergeDocxBuffers(fs.readFileSync(titlePage), buffer);
|
buffer = await mergeDocxBuffers(fs.readFileSync(titlePage), buffer);
|
||||||
} else if (titlePage) {
|
} else if (titlePage) {
|
||||||
state.warnings.push(`Титульник не найден, пропущен: ${titlePage}`);
|
state.warnings.push(`Титульник не найден, пропущен: ${titlePage}`);
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ const DEFAULT_CONFIG = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
lists: {
|
lists: {
|
||||||
bulletChar: '-',
|
// Тире маркера — глиф Symbol U+F02D (рисуется шрифтом Symbol).
|
||||||
|
bulletChar: '\uF02D',
|
||||||
letterSuffix: ')',
|
letterSuffix: ')',
|
||||||
numberSuffix: ')',
|
numberSuffix: ')',
|
||||||
forbiddenLetters: ['ё', 'з', 'й', 'о', 'ч', 'ь', 'ы', 'ъ'],
|
forbiddenLetters: ['ё', 'з', 'й', 'о', 'ч', 'ь', 'ы', 'ъ'],
|
||||||
|
|||||||
@@ -349,9 +349,9 @@ function renderTokens(ctx, tokens, state, opts = {}) {
|
|||||||
const text = tokens[i + 1] ? tokens[i + 1].content : '';
|
const text = tokens[i + 1] ? tokens[i + 1].content : '';
|
||||||
if (mode === 'numbered') {
|
if (mode === 'numbered') {
|
||||||
if (level <= 1) {
|
if (level <= 1) {
|
||||||
if (!(firstH1 && opts.suppressFirstPageBreak)) out.push(pageBreak());
|
const breakBefore = !(firstH1 && opts.suppressFirstPageBreak);
|
||||||
firstH1 = false;
|
firstH1 = false;
|
||||||
out.push(sectionHeading(ctx, text));
|
out.push(sectionHeading(ctx, text, { pageBreakBefore: breakBefore }));
|
||||||
} else if (level === 2) {
|
} else if (level === 2) {
|
||||||
out.push(subsectionHeading(ctx, text));
|
out.push(subsectionHeading(ctx, text));
|
||||||
} else {
|
} else {
|
||||||
@@ -399,7 +399,9 @@ function renderTokens(ctx, tokens, state, opts = {}) {
|
|||||||
function renderSections(ctx, state) {
|
function renderSections(ctx, state) {
|
||||||
resetListInstances();
|
resetListInstances();
|
||||||
const out = [];
|
const out = [];
|
||||||
let first = true;
|
// При наличии титульного листа первая часть тоже должна начинаться
|
||||||
|
// с новой страницы (титульник склеивается перед документом).
|
||||||
|
let first = !ctx.hasTitlePage;
|
||||||
|
|
||||||
for (const section of state.sections) {
|
for (const section of state.sections) {
|
||||||
const entry = section.entry;
|
const entry = section.entry;
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ function alignmentOf(name) {
|
|||||||
|
|
||||||
// ── базовые абзацы ────────────────────────────────────────────────
|
// ── базовые абзацы ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Явное обнуление абзацного отступа. Пустой <w:ind/> наследует отступ
|
||||||
|
// базового стиля, поэтому для центрированных абзацев задаём нули явно.
|
||||||
|
const NO_INDENT = { left: 0, firstLine: 0 };
|
||||||
|
|
||||||
function textRun(ctx, text, opts = {}) {
|
function textRun(ctx, text, opts = {}) {
|
||||||
const t = ctx.typography;
|
const t = ctx.typography;
|
||||||
const run = {
|
const run = {
|
||||||
@@ -73,7 +77,7 @@ function paraRuns(ctx, runs, opts = {}) {
|
|||||||
return new Paragraph({
|
return new Paragraph({
|
||||||
children: runs,
|
children: runs,
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
||||||
indent: opts.noFirstIndent ? {} : { firstLine: ctx.typography.indentFirst },
|
indent: opts.noFirstIndent ? NO_INDENT : { firstLine: ctx.typography.indentFirst },
|
||||||
alignment: opts.alignment || AlignmentType.BOTH,
|
alignment: opts.alignment || AlignmentType.BOTH,
|
||||||
...opts.extra,
|
...opts.extra,
|
||||||
});
|
});
|
||||||
@@ -87,19 +91,17 @@ function emptyLine(ctx) {
|
|||||||
return new Paragraph({
|
return new Paragraph({
|
||||||
children: [textRun(ctx, ' ')],
|
children: [textRun(ctx, ' ')],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── заголовки ─────────────────────────────────────────────────────
|
// ── заголовки ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
function structHeading(ctx, title, opts = {}) {
|
function structHeading(ctx, title, opts = {}) {
|
||||||
const elements = [];
|
|
||||||
if (opts.pageBreakBefore !== false) elements.push(pageBreak());
|
|
||||||
const cfg = ctx.config.headings.struct;
|
const cfg = ctx.config.headings.struct;
|
||||||
elements.push(
|
return new Paragraph({
|
||||||
new Paragraph({
|
|
||||||
heading: HeadingLevel.HEADING_1,
|
heading: HeadingLevel.HEADING_1,
|
||||||
|
pageBreakBefore: opts.pageBreakBefore === true,
|
||||||
children: [textRun(ctx, String(title).toUpperCase())],
|
children: [textRun(ctx, String(title).toUpperCase())],
|
||||||
spacing: {
|
spacing: {
|
||||||
...ctx.typography.lineSpacing,
|
...ctx.typography.lineSpacing,
|
||||||
@@ -107,20 +109,19 @@ function structHeading(ctx, title, opts = {}) {
|
|||||||
after: cfg.spacingAfter,
|
after: cfg.spacingAfter,
|
||||||
},
|
},
|
||||||
alignment: alignmentOf(cfg.align),
|
alignment: alignmentOf(cfg.align),
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
})
|
});
|
||||||
);
|
|
||||||
return elements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function structSection(ctx, title, children, opts = {}) {
|
function structSection(ctx, title, children, opts = {}) {
|
||||||
return flatten(structHeading(ctx, title, opts), children);
|
return flatten(structHeading(ctx, title, opts), children);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sectionHeading(ctx, title) {
|
function sectionHeading(ctx, title, opts = {}) {
|
||||||
const cfg = ctx.config.headings.section;
|
const cfg = ctx.config.headings.section;
|
||||||
return new Paragraph({
|
return new Paragraph({
|
||||||
heading: HeadingLevel.HEADING_1,
|
heading: HeadingLevel.HEADING_1,
|
||||||
|
pageBreakBefore: opts.pageBreakBefore === true,
|
||||||
numbering: { reference: REFS.outline, level: 0 },
|
numbering: { reference: REFS.outline, level: 0 },
|
||||||
children: [textRun(ctx, title)],
|
children: [textRun(ctx, title)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: cfg.spacingBefore, after: cfg.spacingAfter },
|
spacing: { ...ctx.typography.lineSpacing, before: cfg.spacingBefore, after: cfg.spacingAfter },
|
||||||
@@ -152,7 +153,7 @@ function plainHeading(ctx, text, level = 2) {
|
|||||||
children: [textRun(ctx, text)],
|
children: [textRun(ctx, text)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 240, after: 240 },
|
spacing: { ...ctx.typography.lineSpacing, before: 240, after: 240 },
|
||||||
alignment: AlignmentType.LEFT,
|
alignment: AlignmentType.LEFT,
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +177,7 @@ function figureCaption(ctx, label, title) {
|
|||||||
children: [textRun(ctx, `${prefix} ${label} ${dash} ${title}`)],
|
children: [textRun(ctx, `${prefix} ${label} ${dash} ${title}`)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
||||||
alignment: alignmentOf(ctx.config.captions.figure.align),
|
alignment: alignmentOf(ctx.config.captions.figure.align),
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +187,7 @@ function tableCaption(ctx, label, title) {
|
|||||||
children: [textRun(ctx, `${prefix} ${label} ${dash} ${title}`)],
|
children: [textRun(ctx, `${prefix} ${label} ${dash} ${title}`)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 240, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 240, after: 0 },
|
||||||
alignment: alignmentOf(ctx.config.captions.table.align),
|
alignment: alignmentOf(ctx.config.captions.table.align),
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +197,7 @@ function tableContinuation(ctx, label, { pageBreakBefore = false } = {}) {
|
|||||||
children: [textRun(ctx, `${prefix} ${label}`)],
|
children: [textRun(ctx, `${prefix} ${label}`)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 240, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 240, after: 0 },
|
||||||
alignment: alignmentOf(ctx.config.captions.table.align),
|
alignment: alignmentOf(ctx.config.captions.table.align),
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
...(pageBreakBefore ? { pageBreakBefore: true } : {}),
|
...(pageBreakBefore ? { pageBreakBefore: true } : {}),
|
||||||
keepNext: true,
|
keepNext: true,
|
||||||
});
|
});
|
||||||
@@ -273,7 +274,7 @@ function figureBlock(ctx, { label, title, imagePath, notesLines = [] }) {
|
|||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [new ImageRun({ type, data, transformation: scaled })],
|
children: [new ImageRun({ type, data, transformation: scaled })],
|
||||||
alignment: AlignmentType.CENTER,
|
alignment: AlignmentType.CENTER,
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
keepNext: true,
|
keepNext: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -285,7 +286,7 @@ function figureBlock(ctx, { label, title, imagePath, notesLines = [] }) {
|
|||||||
children: [textRun(ctx, line)],
|
children: [textRun(ctx, line)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
||||||
alignment: AlignmentType.CENTER,
|
alignment: AlignmentType.CENTER,
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
keepNext: true,
|
keepNext: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -419,7 +420,7 @@ function mathPara(ctx, mathChildren, formulaNum, opts = {}) {
|
|||||||
children: [textRun(ctx, '\t'), new DocxMath({ children: mathChildren }), textRun(ctx, `\t(${num})`)],
|
children: [textRun(ctx, '\t'), new DocxMath({ children: mathChildren }), textRun(ctx, `\t(${num})`)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
||||||
alignment: AlignmentType.LEFT,
|
alignment: AlignmentType.LEFT,
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
tabStops: [
|
tabStops: [
|
||||||
{ type: TabStopType.CENTER, position: ctx.page.contentWidth / 2 },
|
{ type: TabStopType.CENTER, position: ctx.page.contentWidth / 2 },
|
||||||
{ type: TabStopType.RIGHT, position: ctx.page.contentWidth },
|
{ type: TabStopType.RIGHT, position: ctx.page.contentWidth },
|
||||||
@@ -447,7 +448,7 @@ function whereBlock(ctx, items) {
|
|||||||
return new Paragraph({
|
return new Paragraph({
|
||||||
children: [prefix, ...symbolRuns, textRun(ctx, ` ${dash} ${item.desc}`)],
|
children: [prefix, ...symbolRuns, textRun(ctx, ` ${dash} ${item.desc}`)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: 0, after: 0 },
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
tabStops: [{ type: TabStopType.LEFT, position: ctx.config.formula.whereTabPosition }],
|
tabStops: [{ type: TabStopType.LEFT, position: ctx.config.formula.whereTabPosition }],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -479,7 +480,7 @@ function listParagraphRuns(ctx, runs, reference, level, instance) {
|
|||||||
// ── прочее ────────────────────────────────────────────────────────
|
// ── прочее ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function pageBreak() {
|
function pageBreak() {
|
||||||
return new Paragraph({ children: [new PageBreak()], indent: {} });
|
return new Paragraph({ children: [new PageBreak()], indent: NO_INDENT });
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteSingle(ctx, text) {
|
function noteSingle(ctx, text) {
|
||||||
@@ -524,13 +525,13 @@ function definitionsBlock(ctx, pairs) {
|
|||||||
|
|
||||||
function appendixStart(ctx, letter, title) {
|
function appendixStart(ctx, letter, title) {
|
||||||
return [
|
return [
|
||||||
pageBreak(),
|
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
heading: HeadingLevel.HEADING_1,
|
heading: HeadingLevel.HEADING_1,
|
||||||
|
pageBreakBefore: true,
|
||||||
children: [textRun(ctx, `${ctx.config.appendix.labelPrefix} ${letter.toUpperCase()}`)],
|
children: [textRun(ctx, `${ctx.config.appendix.labelPrefix} ${letter.toUpperCase()}`)],
|
||||||
spacing: { ...ctx.typography.lineSpacing, before: ctx.config.headings.struct.spacingBefore, after: 0 },
|
spacing: { ...ctx.typography.lineSpacing, before: ctx.config.headings.struct.spacingBefore, after: 0 },
|
||||||
alignment: AlignmentType.CENTER,
|
alignment: AlignmentType.CENTER,
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [textRun(ctx, title)],
|
children: [textRun(ctx, title)],
|
||||||
@@ -540,7 +541,7 @@ function appendixStart(ctx, letter, title) {
|
|||||||
after: ctx.config.headings.struct.spacingAfter,
|
after: ctx.config.headings.struct.spacingAfter,
|
||||||
},
|
},
|
||||||
alignment: AlignmentType.CENTER,
|
alignment: AlignmentType.CENTER,
|
||||||
indent: {},
|
indent: NO_INDENT,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,12 @@ function buildNumberingConfig(ctx) {
|
|||||||
left: lists.nestedLevelStep * lvl,
|
left: lists.nestedLevelStep * lvl,
|
||||||
firstLine: typography.indentFirst,
|
firstLine: typography.indentFirst,
|
||||||
});
|
});
|
||||||
const level = (lvl, format, text, { suffix = LevelSuffix.SPACE, indentOverride } = {}) => ({
|
const level = (
|
||||||
|
lvl,
|
||||||
|
format,
|
||||||
|
text,
|
||||||
|
{ suffix = LevelSuffix.SPACE, indentOverride, runFont } = {}
|
||||||
|
) => ({
|
||||||
level: lvl,
|
level: lvl,
|
||||||
format,
|
format,
|
||||||
text,
|
text,
|
||||||
@@ -75,14 +80,14 @@ function buildNumberingConfig(ctx) {
|
|||||||
indent: indentOverride || indent(lvl),
|
indent: indentOverride || indent(lvl),
|
||||||
spacing: { ...typography.lineSpacing, before: 0, after: 0 },
|
spacing: { ...typography.lineSpacing, before: 0, after: 0 },
|
||||||
},
|
},
|
||||||
run,
|
run: runFont ? { ...run, font: runFont } : run,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const outlineLevel = (lvl, text) => ({
|
const outlineLevel = (lvl, text) => ({
|
||||||
level: lvl,
|
level: lvl,
|
||||||
format: LevelFormat.DECIMAL,
|
format: LevelFormat.DECIMAL,
|
||||||
text,
|
text,
|
||||||
suffix: LevelSuffix.TAB,
|
suffix: LevelSuffix.SPACE,
|
||||||
alignment: AlignmentType.LEFT,
|
alignment: AlignmentType.LEFT,
|
||||||
start: 1,
|
start: 1,
|
||||||
style: {
|
style: {
|
||||||
@@ -95,10 +100,10 @@ function buildNumberingConfig(ctx) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dashRef = [
|
const dashRef = [
|
||||||
level(0, LevelFormat.BULLET, lists.bulletChar),
|
level(0, LevelFormat.BULLET, lists.bulletChar, { runFont: 'Symbol' }),
|
||||||
level(1, LevelFormat.RUSSIAN_LOWER, `%2${lists.letterSuffix}`),
|
level(1, LevelFormat.RUSSIAN_LOWER, `%2${lists.letterSuffix}`),
|
||||||
level(2, LevelFormat.DECIMAL, `%3${lists.numberSuffix}`),
|
level(2, LevelFormat.DECIMAL, `%3${lists.numberSuffix}`),
|
||||||
level(3, LevelFormat.BULLET, lists.bulletChar),
|
level(3, LevelFormat.BULLET, lists.bulletChar, { runFont: 'Symbol' }),
|
||||||
];
|
];
|
||||||
const numberRef = [
|
const numberRef = [
|
||||||
level(0, LevelFormat.DECIMAL, `%1${lists.numberSuffix}`),
|
level(0, LevelFormat.DECIMAL, `%1${lists.numberSuffix}`),
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ async function mergeDocxBuffers(prependBuffer, mainBuffer) {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const docx = new DocxMerger({ pageBreak: true }, [prependBin, mainBin]);
|
const docx = new DocxMerger({ pageBreak: false }, [prependBin, mainBin]);
|
||||||
docx.save('nodebuffer', async (data) => {
|
docx.save('nodebuffer', async (data) => {
|
||||||
try {
|
try {
|
||||||
const merged = Buffer.from(data);
|
const merged = Buffer.from(data);
|
||||||
|
|||||||
@@ -30,11 +30,12 @@ const DEFAULTS = {
|
|||||||
},
|
},
|
||||||
slideNumber: {
|
slideNumber: {
|
||||||
show: true,
|
show: true,
|
||||||
x: 12.05,
|
x: 11.7,
|
||||||
y: 6.86,
|
y: 6.86,
|
||||||
w: 1.15,
|
w: 0.8,
|
||||||
h: 0.42,
|
h: 0.42,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
bold: true,
|
||||||
color: "FFFFFF",
|
color: "FFFFFF",
|
||||||
align: "right",
|
align: "right",
|
||||||
},
|
},
|
||||||
@@ -46,10 +47,10 @@ const DEFAULTS = {
|
|||||||
body: { x: 6.7, y: 4.65, w: 5.95, h: 1.7, fontSize: 14, align: "center", color: "white" },
|
body: { x: 6.7, y: 4.65, w: 5.95, h: 1.7, fontSize: 14, align: "center", color: "white" },
|
||||||
},
|
},
|
||||||
section: {
|
section: {
|
||||||
title: { x: 2.0, y: 2.85, w: 9.35, h: 1.8, fontSize: 32, bold: true, align: "center", color: "white" },
|
title: { x: 6.7, y: 2.85, w: 5.95, h: 1.8, fontSize: 32, bold: true, align: "center", color: "white" },
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
title: { x: 0.6, y: 0.06, w: 12.15, h: 0.66, fontSize: 20, bold: false, align: "center", color: "white" },
|
title: { x: 0.6, y: 0.14, w: 12.15, h: 0.7, fontSize: 22, bold: false, align: "center", valign: "middle", color: "white" },
|
||||||
body: { x: 0.75, y: 1.0, w: 11.85, h: 5.55, fontSize: 18, align: "left", color: "ink", valign: "top" },
|
body: { x: 0.75, y: 1.0, w: 11.85, h: 5.55, fontSize: 18, align: "left", color: "ink", valign: "top" },
|
||||||
bulletSize: 18,
|
bulletSize: 18,
|
||||||
subBulletSize: 16,
|
subBulletSize: 16,
|
||||||
|
|||||||
Reference in New Issue
Block a user