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

feat: add custom editor API #312

Merged
merged 20 commits into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion docs/content/en/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ To add your custom parser write a function that gets as an argument the content

**Example**

```
```js{}[nuxt.config.js]
const parseTxt = file => file.split('\n').map(line => line.trim())

// in Config:
Expand All @@ -340,11 +340,33 @@ const parseTxt = file => file.split('\n').map(line => line.trim())
}
```

### `editor`

You can provide a custom editor for editing your markdown files in development. Set the `editor` option to a path to your editor component. The code of the default editor you can find [here](https://github.com/nuxt/content/blob/master/packages/content/templates/editor.vue).


```js{}[nuxt.config.js]
content: {
editor: '~/path/to/editor/component.vue'
}
```

Your component should implement the following:

1. `v-model` for getting the markdown code.
2. prop `isEditing` is a boolean with the information if the editing is started and the component is shown. (this is optional)
3. when finished editing your component has to emit `endEdit`


You should be aware that you get the full markdown file content so this includes the front-matter. You can use `gray-matter` to split and join the markdown and the front-matter.


## Defaults

```js{}[nuxt.config.js]
export default {
content: {
editor: '~/.nuxt/content/editor.vue',
apiPrefix: '_content',
dir: 'content',
fullTextSearchFields: ['title', 'description', 'slug', 'text'],
Expand Down
8 changes: 7 additions & 1 deletion packages/content/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,15 @@ module.exports = async function (moduleOptions) {
fileName: 'content/nuxt-content.dev.vue',
src: join(__dirname, '../templates/nuxt-content.dev.vue'),
options: {
apiPrefix: options.apiPrefixWithBase
apiPrefix: options.apiPrefixWithBase,
editor: options.editor
}
})
// Add dev editor component
this.addTemplate({
fileName: 'content/editor.vue',
src: join(__dirname, '..', 'templates', 'editor.vue')
})
}

function isUrl (string) {
Expand Down
1 change: 1 addition & 0 deletions packages/content/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const logger = require('consola').withScope('@nuxt/content')
const { camelCase } = require('change-case')

const getDefaults = ({ dev = false } = {}) => ({
editor: './editor.vue',
watch: dev,
liveEdit: true,
apiPrefix: '_content',
Expand Down
61 changes: 61 additions & 0 deletions packages/content/templates/editor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<template>
<textarea
v-model="file"
ref="textarea"
@keyup.stop="onType"
@keydown.tab.exact.prevent="onTabRight"
@keydown.tab.shift.prevent="onTabLeft"
@blur="$emit('endEdit')"
/>
</template>
<script>
export default {
props: {
value: String,
isEditing: Boolean
},
data() {
return {
file: ""
};
},
watch: {
value() {
this.file = this.value;
},
isEditing() {
this.onType();
this.$refs.textarea.focus()
},
file() {
this.$emit('input', this.file)
}
},
methods: {
onType() {
const el = this.$refs.textarea;
el.style.height = el.scrollHeight + "px";
},
onTabRight(event) {
let text = this.file,
originalSelectionStart = event.target.selectionStart,
textStart = text.slice(0, originalSelectionStart),
textEnd = text.slice(originalSelectionStart);
this.file = `${textStart}\t${textEnd}`;
event.target.value = this.file; // required to make the cursor stay in place.
event.target.selectionEnd = event.target.selectionStart =
originalSelectionStart + 1;
},
onTabLeft(event) {
let text = this.file,
originalSelectionStart = event.target.selectionStart,
textStart = text.slice(0, originalSelectionStart),
textEnd = text.slice(originalSelectionStart);
this.file = `${textStart.replace(/\t$/, "")}${textEnd}`;
event.target.value = this.file; // required to make the cursor stay in place.
event.target.selectionEnd = event.target.selectionStart =
originalSelectionStart - 1;
}
}
};
</script>
58 changes: 13 additions & 45 deletions packages/content/templates/nuxt-content.dev.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<template>
<div :class="['nuxt-content-container', { 'is-editing': isEditing }]">
<textarea
v-show="isEditing"
v-model="file"
ref="textarea"
class="nuxt-content-editor"
@keyup.stop="onType"
@keydown.tab.exact.prevent="onTabRight"
@keydown.tab.shift.prevent="onTabLeft"
@blur="toggleEdit"
/>
<client-only>
<editor
v-show="isEditing"
:isEditing="isEditing"
v-model="file"
class="nuxt-content-editor"
@endEdit="toggleEdit"
/>
</client-only>
<nuxt-content-dev
ref="content"
:id="id"
Expand All @@ -23,11 +22,13 @@

<script>
import NuxtContent from './nuxt-content'
import Editor from '<%= options.editor %>';

export default {
name: 'NuxtContent',
components: {
NuxtContentDev: NuxtContent
NuxtContentDev: NuxtContent,
Editor
},
props: NuxtContent.props,
data () {
Expand Down Expand Up @@ -73,18 +74,10 @@ export default {
this.isEditing = false
return
}
// Start editing mode
const contentHeight = this.$refs.content.offsetHeight
const actualScrollY = window.scrollY
// Fetch file content
await this.fetchFile()
// Start editing mode
this.isEditing = true
this.$refs.textarea.style.minHeight = `${contentHeight}px`
await this.waitFor(10)
this.$refs.textarea.focus()
this.onType()
await this.waitFor(10)
window.scrollTo(window.scrollX, actualScrollY)
},
async fetchFile () {
this.file = await fetch(this.fileUrl).then(res => res.text())
Expand All @@ -94,31 +87,6 @@ export default {
},
waitFor (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
},
onType () {
const el = this.$refs.textarea

el.style.height = el.scrollHeight + 'px'
},
onTabRight (event) {
let text = this.file,
originalSelectionStart = event.target.selectionStart,
textStart = text.slice(0, originalSelectionStart),
textEnd = text.slice(originalSelectionStart)

this.file = `${textStart}\t${textEnd}`
event.target.value = this.file // required to make the cursor stay in place.
event.target.selectionEnd = event.target.selectionStart = originalSelectionStart + 1
},
onTabLeft (event) {
let text = this.file,
originalSelectionStart = event.target.selectionStart,
textStart = text.slice(0, originalSelectionStart),
textEnd = text.slice(originalSelectionStart)

this.file = `${textStart.replace(/\t$/, '')}${textEnd}`
event.target.value = this.file // required to make the cursor stay in place.
event.target.selectionEnd = event.target.selectionStart = originalSelectionStart - 1
}
}
}
Expand Down
62 changes: 62 additions & 0 deletions test/editor.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const path = require('path')
const { createBrowser } = require('tib')
const { setup, loadConfig, url } = require('@nuxtjs/module-test-utils')

describe('editor option', () => {
let nuxt, browser, page

describe('alias works', () => {
test('lokal alias', async () => {
({ nuxt } = (await setup({
...loadConfig(__dirname),
buildDir: path.join(__dirname, 'fixture', '.nuxt-dev'),
content: { watch: true, editor: '~/.nuxt-dev/content/editor.vue' }
})))

browser = await createBrowser('puppeteer')
page = await browser.page(url('/home'))
const html = await page.getHtml()

expect(html).toMatch(/<div><h1>.*<\/h1>\s*<div\s*.*class="nuxt-content-container"\s*.*><textarea.*><\/textarea>\s*<div\s*.*class="nuxt-content"\s*.*><p.*>This is the home page!<\/p><\/div><\/div><\/div>/)

await nuxt.close()
await browser.close()
}, 60000)

test('module resolution', async () => {
({ nuxt } = (await setup({
...loadConfig(__dirname),
buildDir: path.join(__dirname, 'fixture', '.nuxt-dev'),
content: { watch: true, editor: '@nuxt/content/templates/editor.vue' }
})))

browser = await createBrowser('puppeteer')
page = await browser.page(url('/home'))
const html = await page.getHtml()

expect(html).toMatch(/<div><h1>.*<\/h1>\s*<div\s*.*class="nuxt-content-container"\s*.*><textarea.*><\/textarea>\s*<div\s*.*class="nuxt-content"\s*.*><p.*>This is the home page!<\/p><\/div><\/div><\/div>/)

await nuxt.close()
await browser.close()
}, 60000)
})

describe('replacing works', () => {
test('replacing', async () => {
({ nuxt } = (await setup({
...loadConfig(__dirname),
buildDir: path.join(__dirname, 'fixture', '.nuxt-dev'),
content: { watch: true, editor: '~/components/editor.vue' }
})))

browser = await createBrowser('puppeteer')
page = await browser.page(url('/home'))
const html = await page.getHtml()

expect(html).toMatch(/<div><h1>.*<\/h1>\s*<div\s*.*class="nuxt-content-container"\s*.*><div .*class="editor.*><\/div>\s*<div\s*.*class="nuxt-content"\s*.*><p.*>This is the home page!<\/p><\/div><\/div><\/div>/)

await nuxt.close()
await browser.close()
}, 60000)
})
})
3 changes: 3 additions & 0 deletions test/fixture/components/editor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<div class="editor"></div>
</template>