Skip to content

Commit

Permalink
✨ Add support for horizontal rules, subscript, and superscript from G…
Browse files Browse the repository at this point in the history
…oogle Docs #52
  • Loading branch information
tim-evans committed Jun 20, 2018
2 parents 438f07a + 937dd62 commit 18a632f
Show file tree
Hide file tree
Showing 8 changed files with 2,757 additions and 9 deletions.
2 changes: 2 additions & 0 deletions packages/@atjson/source-gdocs-paste/src/gdocs-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Annotation } from '@atjson/document';

import { GDocsStyleSlice } from './types';

import extractHorizontalRule from './horizontal-rule';
import extractLinkStyles from './link-styles';
import extractListStyles from './list-styles';
import extractParagraphStyles from './paragraph-styles';
Expand All @@ -20,6 +21,7 @@ export default class GDocsParser {
static transforms: Transforms = {
text: extractTextStyles,
paragraph: extractParagraphStyles,
horizontal_rule: extractHorizontalRule,
list: extractListStyles,
link: extractLinkStyles
};
Expand Down
22 changes: 22 additions & 0 deletions packages/@atjson/source-gdocs-paste/src/horizontal-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Annotation } from '@atjson/document';
import { HorizontalRule } from './schema';
import { GDocsStyleSlice } from './types';

export default function extractHorizontalRule(styles: GDocsStyleSlice[]): Annotation[] {
let annotations: HorizontalRule[] = [];

for (let i = 0; i < styles.length; i++) {
let style = styles[i];

if (style === null) continue;

annotations.push({
type: '-gdocs-horizontal_rule',
start: i,
end: i + 1,
attributes: {}
});
}

return annotations;
}
6 changes: 6 additions & 0 deletions packages/@atjson/source-gdocs-paste/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export default class extends Document {

doc.where({ type: '-gdocs-ts_bd' }).set({ type: 'bold' });
doc.where({ type: '-gdocs-ts_it' }).set({ type: 'italic' });
doc.where({ type: '-gdocs-ts_un' }).set({ type: 'underline' });
doc.where({ type: '-gdocs-ts_st' }).set({ type: 'strikethrough' });
doc.where({ type: '-gdocs-ts_va', attributes: { '-gdocs-va': 'sub' } }).set({ type: 'subscript' }).unset('attributes.-gdocs-va');
doc.where({ type: '-gdocs-ts_va', attributes: { '-gdocs-va': 'sup' } }).set({ type: 'superscript' }).unset('attributes.-gdocs-va');

doc.where({ type: '-gdocs-horizontal_rule' }).set({ type: 'horizontal-rule' });

doc.where({ type: '-gdocs-ps_hd' })
.set({ type: 'heading' })
Expand Down
8 changes: 8 additions & 0 deletions packages/@atjson/source-gdocs-paste/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ export interface Underline extends Annotation {
type: '-gdocs-ts_un';
}

export interface Strikethrough extends Annotation {
type: '-gdocs-ts_st';
}

export interface HorizontalRule extends Annotation {
type: '-gdocs-horizontal_rule';
}

export interface Heading extends Annotation {
type: '-gdocs-ps_hd';
attributes: {
Expand Down
18 changes: 17 additions & 1 deletion packages/@atjson/source-gdocs-paste/src/text-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,23 @@ export default function extractTextStyles(styles: GDocsStyleSlice[]): Annotation

if (style === null) continue;

for (let styleType of ['ts_bd', 'ts_it', 'ts_un']) {
// Handle subscript and superscript
if (style.ts_va !== 'nor' && !state.ts_va) {
state.ts_va = {
type: '-gdocs-ts_va',
attributes: {
'-gdocs-va': style.ts_va
},
start: i,
end: -1
};
} else if (style.ts_va === 'nor' && style.ts_va_i === false && state.ts_va) {
state.ts_va.end = i;
annotations.push(state.ts_va);
delete state.ts_va;
}

for (let styleType of ['ts_bd', 'ts_it', 'ts_un', 'ts_st']) {
if (style[styleType] === true && !state[styleType]) {
state[styleType] = { type: '-gdocs-' + styleType, start: i, end: -1 };
} else if (style[styleType] === false && style[styleType + '_i'] === false && state[styleType]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`@atjson/source-gdocs-paste a grab-bag of Google Docs features correctly sets the content 1`] = `
" This paragraph is indented! Here is some underlined text and some italic text and some bold text. This is superscriptTM and some subscript O2 and some strikethrough text. This is a link. This text has a comment.
-
"
`;

exports[`@atjson/source-gdocs-paste relatively complex document correctly sets the content 1`] = `
"This is a simple test.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import Document from '@atjson/document';
import GDocsSource from '@atjson/source-gdocs-paste';
import * as fs from 'fs';
import * as path from 'path';
import GDocsSource from '../src/index';

describe('@atjson/source-gdocs-paste', () => {
describe('relatively complex document', () => {
var atjson;
let atjson;

beforeAll(() => {
// https://docs.google.com/document/d/1xP_M2SchJt81ZuivsO7oix8Q_fCx4PENKJFJR5npFNM/edit
let fixturePath = path.join(__dirname, 'fixtures', 'complex.json');
atjson = JSON.parse(fs.readFileSync(fixturePath));
atjson = JSON.parse(fs.readFileSync(fixturePath).toString());
});

it('has some json', () => {
expect(atjson).toHaveProperty('resolved')
expect(atjson).toHaveProperty('resolved');
});

it('does not throw an error when instantiating with GDocsSource', () => {
Expand Down Expand Up @@ -49,7 +49,7 @@ describe('@atjson/source-gdocs-paste', () => {

it('extracts headings', () => {
let gdocs = new GDocsSource(atjson);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ps_hd').sort((a,b) => a.start - b.start);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ps_hd').sort((a, b) => a.start - b.start);
expect(annotations.length).toEqual(4);

let [a0, a1, a2, a3] = annotations;
Expand All @@ -74,7 +74,7 @@ describe('@atjson/source-gdocs-paste', () => {
expect(annotations.length).toEqual(1);

let a0 = annotations[0];

expect(gdocs.content.substring(a0.start, a0.end)).toEqual('Here’s a numbered list\nAnd another item');
expect(a0.attributes['-gdocs-ls_id']).toEqual('kix.r139o3ivf8cd');
});
Expand Down Expand Up @@ -108,10 +108,92 @@ describe('@atjson/source-gdocs-paste', () => {
});

it('extracts images');
});

describe('a grab-bag of Google Docs features', () => {
let gdocsBuffer;

beforeAll(() => {
// https://docs.google.com/document/d/1xP_M2SchJt81ZuivsO7oix8Q_fCx4PENKJFJR5npFNM/edit
let fixturePath = path.join(__dirname, 'fixtures', 'formats-and-tabs.json');
gdocsBuffer = JSON.parse(fs.readFileSync(fixturePath).toString());
});

it('extracts subscript');
it('has some json', () => {
expect(gdocsBuffer).toHaveProperty('resolved');
});

it('extracts superscript');
it('does not throw an error when instantiating with GDocsSource', () => {
expect(new GDocsSource(gdocsBuffer)).toBeDefined();
});

it('correctly sets the content', () => {
let gdocs = new GDocsSource(gdocsBuffer);
expect(gdocs.content.length).toEqual(219);
expect(gdocs.content).toMatchSnapshot();
});

it('extracts bold', () => {
let gdocs = new GDocsSource(gdocsBuffer);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ts_bd');
expect(annotations.length).toEqual(1);

let [bold] = annotations;
expect(gdocs.content.substring(bold.start, bold.end)).toEqual('bold');
});

it('extracts italic', () => {
let gdocs = new GDocsSource(gdocsBuffer);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ts_it');
expect(annotations.length).toEqual(1);

let [italic] = annotations;
expect(gdocs.content.substring(italic.start, italic.end)).toEqual('italic');
});

it('extracts underline', () => {
let gdocs = new GDocsSource(gdocsBuffer);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ts_un');
expect(annotations.length).toEqual(1);

let [underline] = annotations;
expect(gdocs.content.substring(underline.start, underline.end)).toEqual('underlined');
});

it('extracts horizontal rules', () => {
let gdocs = new GDocsSource(gdocsBuffer);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-horizontal_rule');
expect(annotations.length).toEqual(1);

let [hr] = annotations;
expect(gdocs.content.substring(hr.start, hr.end)).toEqual('-');
});

it('extracts strikethrough', () => {
let gdocs = new GDocsSource(gdocsBuffer);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ts_st');
expect(annotations.length).toEqual(1);

let [strikethrough] = annotations;
expect(gdocs.content.substring(strikethrough.start, strikethrough.end)).toEqual('strikethrough');
});

it('extracts superscript', () => {
let gdocs = new GDocsSource(gdocsBuffer);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ts_va' && a.attributes['-gdocs-va'] === 'sup');
expect(annotations.length).toEqual(1);

let [superscript] = annotations;
expect(gdocs.content.substring(superscript.start, superscript.end)).toEqual('TM');
});

it('extracts subscript', () => {
let gdocs = new GDocsSource(gdocsBuffer);
let annotations = gdocs.annotations.filter(a => a.type === '-gdocs-ts_va' && a.attributes['-gdocs-va'] === 'sub');
expect(annotations.length).toEqual(1);

let [subscript] = annotations;
expect(gdocs.content.substring(subscript.start, subscript.end)).toEqual('2');
});
});
});
Loading

0 comments on commit 18a632f

Please sign in to comment.