Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support org-adapt-indentation #272

Merged
merged 10 commits into from
Apr 6, 2020
5 changes: 5 additions & 0 deletions src/actions/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ export const setShouldLogIntoDrawer = shouldLogIntoDrawer => ({
shouldLogIntoDrawer,
});

export const setShouldNotIndentOnExport = shouldNotIndentOnExport => ({
type: 'SET_SHOULD_NOT_INDENT_ON_EXPORT',
shouldNotIndentOnExport,
});

export const setShouldStoreSettingsInSyncBackend = newShouldStoreSettingsInSyncBackend => {
return (dispatch, getState) => {
dispatch({
Expand Down
3 changes: 2 additions & 1 deletion src/actions/org.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ const doSync = ({
getState().org.present.get('headers'),
getState().org.present.get('todoKeywordSets'),
getState().org.present.get('fileConfigLines'),
getState().org.present.get('linesBeforeHeadings')
getState().org.present.get('linesBeforeHeadings'),
getState().base.get('shouldNotIndentOnExport')
)
)
.then(() => {
Expand Down
37 changes: 31 additions & 6 deletions src/components/OrgFile/OrgFile.unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,21 @@ import { fromJS } from 'immutable';
* This is a convenience wrapper around parsing an org file using
* `parseOrg` and then export it using `exportOrg`.
* @param {String} testOrgFile - contents of an org file
* @param {Boolean} dontIndent - by default false, so indent drawers
*/
function parseAndExportOrgFile(testOrgFile) {
function parseAndExportOrgFile(testOrgFile, dontIndent = false) {
const parsedFile = parseOrg(testOrgFile);
const headers = parsedFile.get('headers');
const todoKeywordSets = parsedFile.get('todoKeywordSets');
const fileConfigLines = parsedFile.get('fileConfigLines');
const linesBeforeHeadings = parsedFile.get('linesBeforeHeadings');
const exportedFile = exportOrg(headers, todoKeywordSets, fileConfigLines, linesBeforeHeadings);
const exportedFile = exportOrg(
headers,
todoKeywordSets,
fileConfigLines,
linesBeforeHeadings,
dontIndent
);
return exportedFile;
}

Expand All @@ -40,25 +47,25 @@ describe('Tests for export', () => {
test('Simple description export of empty description works', () => {
const description = '';
const header = createSimpleHeaderWithDescription(description);
expect(createRawDescriptionText(header, false)).toEqual(description);
expect(createRawDescriptionText(header, false, false)).toEqual(description);
});

test('Simple description export of empty line works', () => {
const description = '\n';
const header = createSimpleHeaderWithDescription(description);
expect(createRawDescriptionText(header, false)).toEqual(description);
expect(createRawDescriptionText(header, false, false)).toEqual(description);
});

test('Simple description export of non-empty line works', () => {
const description = 'abc\n';
const header = createSimpleHeaderWithDescription(description);
expect(createRawDescriptionText(header, false)).toEqual(description);
expect(createRawDescriptionText(header, false, false)).toEqual(description);
});

test('Simple description export of non-empty line without trailing newline works (newline will be added)', () => {
const description = 'abc';
const header = createSimpleHeaderWithDescription(description);
expect(createRawDescriptionText(header, false)).toEqual(`${description}\n`);
expect(createRawDescriptionText(header, false, false)).toEqual(`${description}\n`);
});
});

Expand Down Expand Up @@ -372,6 +379,12 @@ ${description}`;
expect(exportedFile).toEqual(testOrgFile);
});

test('Properties are flush-left when dontIndent is true', () => {
const testOrgFile = readFixture('properties');
const exportedLines = parseAndExportOrgFile(testOrgFile, true).split('\n');
expect(exportedLines[2]).toEqual(':PROPERTIES:');
});

test('Tags are formatted as is default in Emacs', () => {
const testOrgFile = readFixture('tags');
const exportedFile = parseAndExportOrgFile(testOrgFile);
Expand All @@ -385,6 +398,18 @@ ${description}`;
const exportedFile = parseAndExportOrgFile(testOrgFile);
expect(exportedFile).toEqual(testOrgFile);
});
test('Logbook entries are indented by default', () => {
const testOrgFile = readFixture('logbook');
const exportedLines = parseAndExportOrgFile(testOrgFile).split('\n');
expect(exportedLines[1]).toEqual(' :LOGBOOK:');
expect(exportedLines[2].startsWith(' CLOCK:')).toBeTruthy();
});
test('Logbook entries are not indented when dontIndent', () => {
const testOrgFile = readFixture('logbook');
const exportedLines = parseAndExportOrgFile(testOrgFile, true).split('\n');
expect(exportedLines[1]).toEqual(':LOGBOOK:');
expect(exportedLines[2].startsWith('CLOCK:')).toBeTruthy();
});
});
});

Expand Down
3 changes: 2 additions & 1 deletion src/components/OrgFile/components/HeaderContent/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ class HeaderContent extends PureComponent {
}

calculateRawDescription(header) {
return createRawDescriptionText(header, false);
// this is for display only, se we keep the default indentation behaviour
return createRawDescriptionText(header, false, false);
}

handleTextareaRef(textarea) {
Expand Down
21 changes: 21 additions & 0 deletions src/components/Settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const Settings = ({
shouldSyncOnBecomingVisibile,
shouldShowTitleInOrgFile,
shouldLogIntoDrawer,
shouldNotIndentOnExport,
agendaDefaultDeadlineDelayValue,
agendaDefaultDeadlineDelayUnit,
hasUnseenChangelog,
Expand Down Expand Up @@ -57,6 +58,9 @@ const Settings = ({

const handleShouldLogIntoDrawer = () => base.setShouldLogIntoDrawer(!shouldLogIntoDrawer);

const handleShouldNotIndentOnExport = () =>
base.setShouldNotIndentOnExport(!shouldNotIndentOnExport);

const handleShouldStoreSettingsInSyncBackendChange = () =>
base.setShouldStoreSettingsInSyncBackend(!shouldStoreSettingsInSyncBackend);

Expand Down Expand Up @@ -137,6 +141,22 @@ const Settings = ({
<Switch isEnabled={shouldLogIntoDrawer} onToggle={handleShouldLogIntoDrawer} />
</div>

<div className="setting-container">
<div className="setting-label">
Disable hard indent on Org export
<div className="setting-label__description">
By default, the metadata body (including deadlines and drawers) of an exported org
heading is indented according to its level. If instead you prefer to keep your body text
flush-left, i.e.{' '}
<a href="https://orgmode.org/manual/Hard-indentation.html">
<code>(setq org-adapt-indentation nil)</code>
</a>
, then activate this setting. The raw content text is left unchanged.
</div>
</div>
<Switch isEnabled={shouldNotIndentOnExport} onToggle={handleShouldNotIndentOnExport} />
</div>

<div className="setting-container">
<div className="setting-label">
Store settings in sync backend
Expand Down Expand Up @@ -233,6 +253,7 @@ const mapStateToProps = (state, props) => {
shouldSyncOnBecomingVisibile: state.base.get('shouldSyncOnBecomingVisibile'),
shouldShowTitleInOrgFile: state.base.get('shouldShowTitleInOrgFile'),
shouldLogIntoDrawer: state.base.get('shouldLogIntoDrawer'),
shouldNotIndentOnExport: state.base.get('shouldNotIndentOnExport'),
hasUnseenChangelog: state.base.get('hasUnseenChangelog'),
};
};
Expand Down
33 changes: 28 additions & 5 deletions src/lib/export_org.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,23 @@ export const generateTitleLine = (header, includeStars) => {
return contents;
};

export const exportOrg = (headers, todoKeywordSets, fileConfigLines, linesBeforeHeadings) => {
/**
* Convert state data into a fully rendered Orgmode file text.
*
* @param {*} headers Full list of org headings from redux state.
* @param {*} todoKeywordSets
* @param {*} fileConfigLines List of all "#+VAR:" config lines in the file.
* @param {*} linesBeforeHeadings Text that occurs before the first heading.
* @param {boolean} dontIndent Default false means indent drawers according to
* nesting level, else keep everything flush-left. Description is kept as is.
*/
export const exportOrg = (
headers,
todoKeywordSets,
fileConfigLines,
linesBeforeHeadings,
dontIndent
) => {
let configContent = '';

if (fileConfigLines.size > 0) {
Expand All @@ -267,18 +283,25 @@ export const exportOrg = (headers, todoKeywordSets, fileConfigLines, linesBefore
configContent = configContent + '\n';
}

const headerContent = headers.map(x => createRawDescriptionText(x, true)).join('');
const headerContent = headers.map(x => createRawDescriptionText(x, true, dontIndent)).join('');

return configContent + headerContent;
};

export const createRawDescriptionText = (header, includeTitle) => {
/**
* Transform state data of complete org element / header into rendered text.
*
* @param {*} header Immutable map with header data
* @param {boolean} includeTitle Render the full orgmode title
* @param {boolean} dontIndent If true keep all text flush-left, else (DEFAULT) indent according to nesting level
*/
export const createRawDescriptionText = (header, includeTitle, dontIndent) => {
// To simplify access to properties:
header = header.toJS();

// Pad things like planning items and tables appropriately
// considering the nestingLevel of the header.
const indentation = ' '.repeat(header.nestingLevel + 1);
// considering the nestingLevel of the header, unless that is explicitly disabled
const indentation = dontIndent ? '' : ' '.repeat(header.nestingLevel + 1);
let contents = '';

if (includeTitle) {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/parse_org.unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ function parseAndExportOrgFile(testOrgFile) {
const todoKeywordSets = parsedFile.get('todoKeywordSets');
const fileConfigLines = parsedFile.get('fileConfigLines');
const linesBeforeHeadings = parsedFile.get('linesBeforeHeadings');
const exportedFile = exportOrg(headers, todoKeywordSets, fileConfigLines, linesBeforeHeadings);
const exportedFile = exportOrg(
headers, todoKeywordSets, fileConfigLines, linesBeforeHeadings, false);
return exportedFile;
}

Expand Down
10 changes: 10 additions & 0 deletions src/reducers/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ const setShouldShowTitleInOrgFile = (state, action) =>
const setShouldLogIntoDrawer = (state, action) =>
state.set('shouldLogIntoDrawer', action.shouldLogIntoDrawer);

/**
* When enabled, keep all heading body text flush-left. When disabled (the
* default) indent the body text of headings according to the nesting level of
* the heading.
*/
const setShouldNotIndentOnExport = (state, action) =>
state.set('shouldNotIndentOnExport', action.shouldNotIndentOnExport);

const setHasUnseenChangelog = (state, action) =>
state.set('hasUnseenChangelog', action.newHasUnseenChangelog);

Expand Down Expand Up @@ -129,6 +137,8 @@ export default (state = Map(), action) => {
return setShouldShowTitleInOrgFile(state, action);
case 'SET_SHOULD_LOG_INTO_DRAWER':
return setShouldLogIntoDrawer(state, action);
case 'SET_SHOULD_NOT_INDENT_ON_EXPORT':
return setShouldNotIndentOnExport(state, action);
case 'SET_HAS_UNSEEN_CHANGELOG':
return setHasUnseenChangelog(state, action);
case 'SET_LAST_SEEN_CHANGELOG_HEADER':
Expand Down
6 changes: 6 additions & 0 deletions src/util/settings_persister.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export const persistableFields = [
type: 'boolean',
shouldStoreInConfig: true,
},
{
category: 'base',
name: 'shouldNotIndentOnExport',
type: 'boolean',
shouldStoreInConfig: true,
},
{
category: 'capture',
name: 'captureTemplates',
Expand Down