mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2025-01-18 05:22:28 -05:00
Implement unordered and ordered lists (#88)
* wip * implement bullet points * needs more ifs * refactor * revert * refactor * fix and refactor * refactor * refactor * refactor * add ordered class * wip * wip * fixes * package.json * little refactors * base list * fixes * abstract baselist * refactors * refactor * changeset * fix eslint issue * fix * fixes * fixes --------- Co-authored-by: Alex Sánchez <sion333@gmail.com>
This commit is contained in:
parent
afa47af0a6
commit
2920ac297b
44 changed files with 362 additions and 123 deletions
5
.changeset/curly-clouds-tease.md
Normal file
5
.changeset/curly-clouds-tease.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Ordered and unordered list support
|
|
@ -21,6 +21,9 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }]
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{ ignoreRestSiblings: true, argsIgnorePattern: '^_' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -12,6 +12,7 @@
|
||||||
"react": "^18.3",
|
"react": "^18.3",
|
||||||
"react-dom": "^18.3",
|
"react-dom": "^18.3",
|
||||||
"react-hook-form": "^7.51",
|
"react-hook-form": "^7.51",
|
||||||
|
"romans": "^2.0",
|
||||||
"slugify": "^1.6",
|
"slugify": "^1.6",
|
||||||
"svg-path-parser": "^1.1"
|
"svg-path-parser": "^1.1"
|
||||||
},
|
},
|
||||||
|
@ -6550,6 +6551,11 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/romans": {
|
||||||
|
"version": "2.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/romans/-/romans-2.0.15.tgz",
|
||||||
|
"integrity": "sha512-/0/Wdz+Q948fkUlBt+JUgkxdYAmlBStLoSIqpBxaEDg9NGORrGaMCu9iYk8eRsiwRe2cLWflJswZP6g9vGIE1w=="
|
||||||
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"react": "^18.3",
|
"react": "^18.3",
|
||||||
"react-dom": "^18.3",
|
"react-dom": "^18.3",
|
||||||
"react-hook-form": "^7.51",
|
"react-hook-form": "^7.51",
|
||||||
|
"romans": "^2.0",
|
||||||
"slugify": "^1.6",
|
"slugify": "^1.6",
|
||||||
"svg-path-parser": "^1.1"
|
"svg-path-parser": "^1.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { transformDocumentNode } from '@plugin/transformers';
|
import { transformDocumentNode } from '@plugin/transformers';
|
||||||
|
|
||||||
import { findAllTextNodes } from './findAllTextnodes';
|
import { findAllTextNodes } from './findAllTextnodes';
|
||||||
import { setCustomFontId } from './translators/text/custom';
|
import { setCustomFontId } from './translators/text/font/custom';
|
||||||
|
|
||||||
figma.showUI(__html__, { themeColors: true, height: 300, width: 400 });
|
figma.showUI(__html__, { themeColors: true, height: 300, width: 400 });
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { isGoogleFont } from './translators/text/gfonts';
|
import { isGoogleFont } from './translators/text/font/gfonts';
|
||||||
import { isLocalFont } from './translators/text/local';
|
import { isLocalFont } from './translators/text/font/local';
|
||||||
|
|
||||||
export const findAllTextNodes = async () => {
|
export const findAllTextNodes = async () => {
|
||||||
await figma.loadAllPagesAsync();
|
await figma.loadAllPagesAsync();
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { transformFills } from '@plugin/transformers/partials';
|
import { transformFills } from '@plugin/transformers/partials';
|
||||||
import {
|
import { transformTextStyle, translateStyleTextSegments } from '@plugin/translators/text';
|
||||||
transformTextStyle,
|
import { translateGrowType, translateVerticalAlign } from '@plugin/translators/text/properties';
|
||||||
translateGrowType,
|
|
||||||
translateStyleTextSegments,
|
|
||||||
translateVerticalAlign
|
|
||||||
} from '@plugin/translators/text';
|
|
||||||
|
|
||||||
import { TextShape } from '@ui/lib/types/shapes/textShape';
|
import { TextShape } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
||||||
|
@ -17,6 +13,8 @@ export const transformText = (node: TextNode): Partial<TextShape> => {
|
||||||
'letterSpacing',
|
'letterSpacing',
|
||||||
'textCase',
|
'textCase',
|
||||||
'textDecoration',
|
'textDecoration',
|
||||||
|
'indentation',
|
||||||
|
'listOptions',
|
||||||
'fills'
|
'fills'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export * from './translateBlendMode';
|
export * from './translateBlendMode';
|
||||||
export * from './translateShadowEffects';
|
|
||||||
export * from './translateFills';
|
export * from './translateFills';
|
||||||
|
export * from './translateShadowEffects';
|
||||||
export * from './translateStrokes';
|
export * from './translateStrokes';
|
||||||
export * from './translateVectorPaths';
|
export * from './translateVectorPaths';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getCustomFontId, translateFontVariantId } from '@plugin/translators/text/custom';
|
import { getCustomFontId, translateFontVariantId } from '@plugin/translators/text/font/custom';
|
||||||
|
|
||||||
import { FontId } from '@ui/lib/types/shapes/textShape';
|
import { FontId } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export * from './googleFont';
|
export * from './googleFont';
|
||||||
export * from './translateGoogleFont';
|
|
||||||
export * from './translateFontVariantId';
|
export * from './translateFontVariantId';
|
||||||
|
export * from './translateGoogleFont';
|
|
@ -1,6 +1,6 @@
|
||||||
import slugify from 'slugify';
|
import slugify from 'slugify';
|
||||||
|
|
||||||
import { translateFontVariantId } from '@plugin/translators/text/gfonts';
|
import { translateFontVariantId } from '@plugin/translators/text/font/gfonts';
|
||||||
|
|
||||||
import { FontId } from '@ui/lib/types/shapes/textShape';
|
import { FontId } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
1
plugin-src/translators/text/font/index.ts
Normal file
1
plugin-src/translators/text/font/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './translateFontId';
|
|
@ -1,3 +1,3 @@
|
||||||
export * from './localFont';
|
export * from './localFont';
|
||||||
export * from './translateLocalFont';
|
|
||||||
export * from './translateFontVariantId';
|
export * from './translateFontVariantId';
|
||||||
|
export * from './translateLocalFont';
|
|
@ -1,4 +1,4 @@
|
||||||
import { LocalFont, translateFontVariantId } from '@plugin/translators/text/local';
|
import { LocalFont, translateFontVariantId } from '@plugin/translators/text/font/local';
|
||||||
|
|
||||||
import { FontId } from '@ui/lib/types/shapes/textShape';
|
import { FontId } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
|
@ -1,11 +1 @@
|
||||||
export * from './translateFontId';
|
|
||||||
export * from './translateFontStyle';
|
|
||||||
export * from './translateGrowType';
|
|
||||||
export * from './translateHorizontalAlign';
|
|
||||||
export * from './translateLetterSpacing';
|
|
||||||
export * from './translateLineHeight';
|
|
||||||
export * from './translateParagraphProperties';
|
|
||||||
export * from './translateStyleTextSegments';
|
export * from './translateStyleTextSegments';
|
||||||
export * from './translateTextDecoration';
|
|
||||||
export * from './translateTextTransform';
|
|
||||||
export * from './translateVerticalAlign';
|
|
||||||
|
|
79
plugin-src/translators/text/paragraph/List.ts
Normal file
79
plugin-src/translators/text/paragraph/List.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { StyleTextSegment } from '@plugin/translators/text/paragraph/translateParagraphProperties';
|
||||||
|
|
||||||
|
import { TextNode as PenpotTextNode } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
||||||
|
import { ListTypeFactory } from './ListTypeFactory';
|
||||||
|
|
||||||
|
type Level = {
|
||||||
|
style: PenpotTextNode;
|
||||||
|
counter: number;
|
||||||
|
type: ListType;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ListType = 'ORDERED' | 'UNORDERED';
|
||||||
|
|
||||||
|
export class List {
|
||||||
|
private levels: Map<number, Level> = new Map();
|
||||||
|
private indentation = 0;
|
||||||
|
protected counter: number[] = [];
|
||||||
|
private listTypeFactory = new ListTypeFactory();
|
||||||
|
|
||||||
|
public update(textNode: PenpotTextNode, segment: StyleTextSegment): void {
|
||||||
|
if (segment.indentation < this.indentation) {
|
||||||
|
for (let i = segment.indentation + 1; i <= this.indentation; i++) {
|
||||||
|
this.levels.delete(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let level = this.levels.get(segment.indentation);
|
||||||
|
|
||||||
|
if (!level || level.type !== this.getListType(segment)) {
|
||||||
|
level = {
|
||||||
|
style: this.createStyle(textNode, segment.indentation),
|
||||||
|
counter: 0,
|
||||||
|
type: this.getListType(segment)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.levels.set(segment.indentation, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
level.counter++;
|
||||||
|
this.indentation = segment.indentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentList(textNode: PenpotTextNode, segment: StyleTextSegment): PenpotTextNode {
|
||||||
|
const level = this.levels.get(segment.indentation);
|
||||||
|
if (level === undefined) {
|
||||||
|
throw new Error('Levels not updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
const listType = this.listTypeFactory.getListType(segment.listOptions);
|
||||||
|
|
||||||
|
return this.updateCurrentSymbol(
|
||||||
|
listType.getCurrentSymbol(level.counter, segment.indentation),
|
||||||
|
level.style
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getListType(segment: StyleTextSegment): ListType {
|
||||||
|
if (segment.listOptions.type === 'NONE') {
|
||||||
|
throw new Error('List type not valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return segment.listOptions.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createStyle(node: PenpotTextNode, indentation: number): PenpotTextNode {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
text: `${'\t'.repeat(Math.max(0, indentation - 1))}{currentSymbol}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCurrentSymbol(character: string, currentStyle: PenpotTextNode): PenpotTextNode {
|
||||||
|
return {
|
||||||
|
...currentStyle,
|
||||||
|
text: currentStyle.text.replace('{currentSymbol}', character)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
3
plugin-src/translators/text/paragraph/ListType.ts
Normal file
3
plugin-src/translators/text/paragraph/ListType.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export interface ListType {
|
||||||
|
getCurrentSymbol(number: number, indentation: number): string;
|
||||||
|
}
|
19
plugin-src/translators/text/paragraph/ListTypeFactory.ts
Normal file
19
plugin-src/translators/text/paragraph/ListTypeFactory.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { ListType } from './ListType';
|
||||||
|
import { OrderedListType } from './OrderedListType';
|
||||||
|
import { UnorderedListType } from './UnorderedListType';
|
||||||
|
|
||||||
|
export class ListTypeFactory {
|
||||||
|
private unorderedList = new UnorderedListType();
|
||||||
|
private orderedList = new OrderedListType();
|
||||||
|
|
||||||
|
public getListType(textListOptions: TextListOptions): ListType {
|
||||||
|
switch (textListOptions.type) {
|
||||||
|
case 'ORDERED':
|
||||||
|
return this.orderedList;
|
||||||
|
case 'UNORDERED':
|
||||||
|
return this.unorderedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('List type not valid');
|
||||||
|
}
|
||||||
|
}
|
42
plugin-src/translators/text/paragraph/OrderedListType.ts
Normal file
42
plugin-src/translators/text/paragraph/OrderedListType.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import * as romans from 'romans';
|
||||||
|
|
||||||
|
import { ListType } from './ListType';
|
||||||
|
|
||||||
|
export class OrderedListType implements ListType {
|
||||||
|
public getCurrentSymbol(number: number, indentation: number): string {
|
||||||
|
let symbol = '. ';
|
||||||
|
switch (indentation % 3) {
|
||||||
|
case 0:
|
||||||
|
symbol = romans.romanize(number).toLowerCase() + symbol;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
symbol = this.letterOrderedList(number) + symbol;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
symbol = number.toString() + symbol;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
private letterOrderedList(number: number): string {
|
||||||
|
let result = '';
|
||||||
|
|
||||||
|
while (number > 0) {
|
||||||
|
let letterCode = number % 26;
|
||||||
|
|
||||||
|
if (letterCode === 0) {
|
||||||
|
letterCode = 26;
|
||||||
|
number = Math.floor(number / 26) - 1;
|
||||||
|
} else {
|
||||||
|
number = Math.floor(number / 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = String.fromCharCode(letterCode + 96) + result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
93
plugin-src/translators/text/paragraph/Paragraph.ts
Normal file
93
plugin-src/translators/text/paragraph/Paragraph.ts
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import { TextNode as PenpotTextNode } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
||||||
|
import { List } from './List';
|
||||||
|
import { StyleTextSegment } from './translateParagraphProperties';
|
||||||
|
|
||||||
|
export class Paragraph {
|
||||||
|
private isParagraphStarting = false;
|
||||||
|
private isPreviousNodeAList = false;
|
||||||
|
private firstTextNode: PenpotTextNode | null = null;
|
||||||
|
private list = new List();
|
||||||
|
|
||||||
|
public format(
|
||||||
|
node: TextNode,
|
||||||
|
textNode: PenpotTextNode,
|
||||||
|
segment: StyleTextSegment
|
||||||
|
): PenpotTextNode[] {
|
||||||
|
const textNodes: PenpotTextNode[] = [];
|
||||||
|
|
||||||
|
const spacing = this.applySpacing(segment, node);
|
||||||
|
if (spacing) textNodes.push(spacing);
|
||||||
|
|
||||||
|
const indentation = this.applyIndentation(textNode, segment, node);
|
||||||
|
if (indentation) textNodes.push(indentation);
|
||||||
|
|
||||||
|
textNodes.push(textNode);
|
||||||
|
|
||||||
|
this.isPreviousNodeAList = segment.listOptions.type !== 'NONE';
|
||||||
|
this.isParagraphStarting = textNode.text === '\n';
|
||||||
|
|
||||||
|
return textNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyIndentation(
|
||||||
|
textNode: PenpotTextNode,
|
||||||
|
segment: StyleTextSegment,
|
||||||
|
node: TextNode
|
||||||
|
): PenpotTextNode | undefined {
|
||||||
|
if (this.isParagraphStarting || this.isFirstTextNode(textNode)) {
|
||||||
|
this.list.update(textNode, segment);
|
||||||
|
|
||||||
|
return segment.listOptions.type !== 'NONE'
|
||||||
|
? this.list.getCurrentList(textNode, segment)
|
||||||
|
: this.segmentIndent(node.paragraphIndent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private applySpacing(segment: StyleTextSegment, node: TextNode): PenpotTextNode | undefined {
|
||||||
|
if (this.isParagraphStarting) {
|
||||||
|
const isList = segment.listOptions.type !== 'NONE';
|
||||||
|
|
||||||
|
return this.segmentParagraphSpacing(
|
||||||
|
this.isPreviousNodeAList && isList ? node.listSpacing : node.paragraphSpacing
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isFirstTextNode(textNode: PenpotTextNode) {
|
||||||
|
if (this.firstTextNode === null) {
|
||||||
|
this.firstTextNode = textNode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private segmentIndent(indent: number): PenpotTextNode {
|
||||||
|
return {
|
||||||
|
text: ' '.repeat(indent),
|
||||||
|
fontId: 'sourcesanspro',
|
||||||
|
fontVariantId: 'regular',
|
||||||
|
fontSize: '5',
|
||||||
|
fontStyle: 'normal',
|
||||||
|
fontWeight: '400',
|
||||||
|
lineHeight: 1,
|
||||||
|
letterSpacing: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private segmentParagraphSpacing(paragraphSpacing: number): PenpotTextNode | undefined {
|
||||||
|
if (paragraphSpacing === 0) return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: '\n',
|
||||||
|
fontId: 'sourcesanspro',
|
||||||
|
fontVariantId: 'regular',
|
||||||
|
fontSize: paragraphSpacing.toString(),
|
||||||
|
fontStyle: 'normal',
|
||||||
|
fontWeight: '400',
|
||||||
|
lineHeight: 1,
|
||||||
|
letterSpacing: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { ListType } from './ListType';
|
||||||
|
|
||||||
|
export class UnorderedListType implements ListType {
|
||||||
|
public getCurrentSymbol(_number: number, _indentation: number): string {
|
||||||
|
return ' • ';
|
||||||
|
}
|
||||||
|
}
|
7
plugin-src/translators/text/paragraph/index.ts
Normal file
7
plugin-src/translators/text/paragraph/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './List';
|
||||||
|
export * from './ListType';
|
||||||
|
export * from './ListTypeFactory';
|
||||||
|
export * from './OrderedListType';
|
||||||
|
export * from './Paragraph';
|
||||||
|
export * from './translateParagraphProperties';
|
||||||
|
export * from './UnorderedListType';
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { TextNode as PenpotTextNode } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
||||||
|
import { Paragraph } from './Paragraph';
|
||||||
|
|
||||||
|
export type StyleTextSegment = Pick<
|
||||||
|
StyledTextSegment,
|
||||||
|
| 'characters'
|
||||||
|
| 'start'
|
||||||
|
| 'end'
|
||||||
|
| 'fontName'
|
||||||
|
| 'fontSize'
|
||||||
|
| 'fontWeight'
|
||||||
|
| 'lineHeight'
|
||||||
|
| 'letterSpacing'
|
||||||
|
| 'textCase'
|
||||||
|
| 'textDecoration'
|
||||||
|
| 'indentation'
|
||||||
|
| 'listOptions'
|
||||||
|
| 'fills'
|
||||||
|
>;
|
||||||
|
|
||||||
|
type PartialTranslation = {
|
||||||
|
textNodes: PenpotTextNode[];
|
||||||
|
segment: StyleTextSegment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const translateParagraphProperties = (
|
||||||
|
node: TextNode,
|
||||||
|
partials: { textNode: PenpotTextNode; segment: StyleTextSegment }[]
|
||||||
|
): PenpotTextNode[] => {
|
||||||
|
const splitSegments: PartialTranslation[] = [];
|
||||||
|
|
||||||
|
partials.forEach(({ textNode, segment }) => {
|
||||||
|
splitSegments.push({
|
||||||
|
textNodes: splitTextNodeByEOL(textNode),
|
||||||
|
segment
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return addParagraphProperties(node, splitSegments);
|
||||||
|
};
|
||||||
|
|
||||||
|
const splitTextNodeByEOL = (node: PenpotTextNode): PenpotTextNode[] => {
|
||||||
|
const split = node.text.split(/(\n)/).filter(text => text !== '');
|
||||||
|
|
||||||
|
return split.map(text => ({
|
||||||
|
...node,
|
||||||
|
text: text
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addParagraphProperties = (
|
||||||
|
node: TextNode,
|
||||||
|
partials: PartialTranslation[]
|
||||||
|
): PenpotTextNode[] => {
|
||||||
|
const formattedParagraphs: PenpotTextNode[] = [];
|
||||||
|
const paragraph = new Paragraph();
|
||||||
|
|
||||||
|
partials.forEach(({ textNodes, segment }) =>
|
||||||
|
textNodes.forEach(textNode => {
|
||||||
|
formattedParagraphs.push(...paragraph.format(node, textNode, segment));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return formattedParagraphs;
|
||||||
|
};
|
8
plugin-src/translators/text/properties/index.ts
Normal file
8
plugin-src/translators/text/properties/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export * from './translateFontStyle';
|
||||||
|
export * from './translateGrowType';
|
||||||
|
export * from './translateHorizontalAlign';
|
||||||
|
export * from './translateLetterSpacing';
|
||||||
|
export * from './translateLineHeight';
|
||||||
|
export * from './translateTextDecoration';
|
||||||
|
export * from './translateTextTransform';
|
||||||
|
export * from './translateVerticalAlign';
|
|
@ -1,75 +0,0 @@
|
||||||
import { TextNode as PenpotTextNode } from '@ui/lib/types/shapes/textShape';
|
|
||||||
|
|
||||||
export const translateParagraphProperties = (
|
|
||||||
node: TextNode,
|
|
||||||
segments: PenpotTextNode[]
|
|
||||||
): PenpotTextNode[] => {
|
|
||||||
if (node.paragraphSpacing === 0 && node.paragraphIndent === 0) return segments;
|
|
||||||
|
|
||||||
const splitSegments: PenpotTextNode[] = [segmentIndent(node.paragraphIndent)];
|
|
||||||
|
|
||||||
segments.forEach(segment => {
|
|
||||||
splitSegments.push(...splitTextNodeByEOL(segment));
|
|
||||||
});
|
|
||||||
|
|
||||||
return addParagraphProperties(splitSegments, node.paragraphIndent, node.paragraphSpacing);
|
|
||||||
};
|
|
||||||
|
|
||||||
const splitTextNodeByEOL = (node: PenpotTextNode): PenpotTextNode[] => {
|
|
||||||
const split = node.text.split(/(\n)/).filter(text => text !== '');
|
|
||||||
|
|
||||||
return split.map(text => ({
|
|
||||||
...node,
|
|
||||||
text: text
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const addParagraphProperties = (
|
|
||||||
nodes: PenpotTextNode[],
|
|
||||||
indent: number,
|
|
||||||
paragraphSpacing: number
|
|
||||||
): PenpotTextNode[] => {
|
|
||||||
const indentedTextNodes: PenpotTextNode[] = [];
|
|
||||||
|
|
||||||
nodes.forEach(node => {
|
|
||||||
indentedTextNodes.push(node);
|
|
||||||
|
|
||||||
if (node.text !== '\n') return;
|
|
||||||
|
|
||||||
if (paragraphSpacing !== 0) {
|
|
||||||
indentedTextNodes.push(segmentParagraphSpacing(paragraphSpacing));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indent !== 0) {
|
|
||||||
indentedTextNodes.push(segmentIndent(indent));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return indentedTextNodes;
|
|
||||||
};
|
|
||||||
|
|
||||||
const segmentIndent = (indent: number): PenpotTextNode => {
|
|
||||||
return {
|
|
||||||
text: ' '.repeat(indent),
|
|
||||||
fontId: 'sourcesanspro',
|
|
||||||
fontVariantId: 'regular',
|
|
||||||
fontSize: '5',
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontWeight: '400',
|
|
||||||
lineHeight: 1,
|
|
||||||
letterSpacing: 0
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const segmentParagraphSpacing = (paragraphSpacing: number): PenpotTextNode => {
|
|
||||||
return {
|
|
||||||
text: '\n',
|
|
||||||
fontId: 'sourcesanspro',
|
|
||||||
fontVariantId: 'regular',
|
|
||||||
fontSize: paragraphSpacing.toString(),
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontWeight: '400',
|
|
||||||
lineHeight: 1,
|
|
||||||
letterSpacing: 0
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,41 +1,27 @@
|
||||||
import { translateFills } from '@plugin/translators';
|
import { translateFills } from '@plugin/translators';
|
||||||
|
import { translateFontId } from '@plugin/translators/text/font';
|
||||||
|
import { StyleTextSegment, translateParagraphProperties } from '@plugin/translators/text/paragraph';
|
||||||
import {
|
import {
|
||||||
translateFontId,
|
|
||||||
translateFontStyle,
|
translateFontStyle,
|
||||||
translateHorizontalAlign,
|
translateHorizontalAlign,
|
||||||
translateLetterSpacing,
|
translateLetterSpacing,
|
||||||
translateLineHeight,
|
translateLineHeight,
|
||||||
translateParagraphProperties,
|
|
||||||
translateTextDecoration,
|
translateTextDecoration,
|
||||||
translateTextTransform
|
translateTextTransform
|
||||||
} from '@plugin/translators/text';
|
} from '@plugin/translators/text/properties';
|
||||||
|
|
||||||
import { TextNode as PenpotTextNode, TextStyle } from '@ui/lib/types/shapes/textShape';
|
import { TextNode as PenpotTextNode, TextStyle } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
||||||
type StyleTextSegment = Pick<
|
|
||||||
StyledTextSegment,
|
|
||||||
| 'characters'
|
|
||||||
| 'start'
|
|
||||||
| 'end'
|
|
||||||
| 'fontName'
|
|
||||||
| 'fontSize'
|
|
||||||
| 'fontWeight'
|
|
||||||
| 'lineHeight'
|
|
||||||
| 'letterSpacing'
|
|
||||||
| 'textCase'
|
|
||||||
| 'textDecoration'
|
|
||||||
| 'fills'
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const translateStyleTextSegments = (
|
export const translateStyleTextSegments = (
|
||||||
node: TextNode,
|
node: TextNode,
|
||||||
segments: StyleTextSegment[]
|
segments: StyleTextSegment[]
|
||||||
): PenpotTextNode[] => {
|
): PenpotTextNode[] => {
|
||||||
const textNodes = segments.map(segment => {
|
const partials = segments.map(segment => ({
|
||||||
return translateStyleTextSegment(node, segment);
|
textNode: translateStyleTextSegment(node, segment),
|
||||||
});
|
segment
|
||||||
|
}));
|
||||||
|
|
||||||
return translateParagraphProperties(node, textNodes);
|
return translateParagraphProperties(node, partials);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformTextStyle = (
|
export const transformTextStyle = (
|
||||||
|
|
Loading…
Add table
Reference in a new issue