Pull to refresh

Создание блога с помощью Nuxt Content (часть вторая)

Reading time12 min
Views4K
Original author: Debbie O'Brien

обложка


Продолжаем изучать Nuxt Content.


Первая часть доступна здесь.


В этой части мы узнаем как стилизовать код в статьях, сортировать статьи по различным параметрам, работать с API Content и многое другое.



Посмотреть демо /
Исходный код


Оглавление:



Добавление компонента Author и передача ему входящих параметров


Другое преимущество переменных в YAML разделе заключается в том, что мы можем сделать их доступными для компонентов через props. Например, у нас может быть компонент об авторе, и если у нас есть несколько человек, пишущих для блога, данные об авторе будут меняться от статьи к статье. В нашем markdown файле мы можем добавить новый объект в шапку файла, который будет содержать имя автора, биографию и изображение.


---
author:
  name: Benjamin
  bio: All about Benjamin
  image: https://images.unsplash.com/.....
---

Теперь мы можем создать компонент автора.


touch components/global/Author.vue

В нем мы создадим div, принимающий динамические переменные: с изображением, заголовком Author, именем и биографией автора.


<template>
  <div>
    <img :src="author.image" />
    <div>
      <h4>Author</h4>
      <p>{{ author.name }}</p>
      <p>{{ author.bio }}</p>
    </div>
  </div>
</template>

Стили были удалены из этих примеров, вы можете добавлять стили самостоятельно или копировать стили отсюда.

Затем в нашем теге скрипта мы можем добавить в props переменную author, тип которой обозначим как Object, а для required установим значение true.


<script>
  export default {
    props: {
      author: {
        type: Object,
        required: true
      }
    }
  }
</script>

Чтобы использовать компонент, можно добавить его в файл my-first-blog-post.md и передать ему объект author.


<author :author="author" />

Нельзя использовать самозакрывающиеся теги в markdown, например, <author: author =" author "/> не будет работать.

Размещение компонента в файле с разметкой означает, что нам придется повторять его для каждой статьи. В этом случае было бы лучше добавить его прямо в шаблон _slag. Только теперь нужно будет изменить свойство author на article.author.


<template>
  <article>
    <h1>{{ article.title }}</h1>
    <p>{{ article.description }}</p>
    <img :src="article.img" :alt="article.alt" />
    <p>Article last updated: {{ formatDate(article.updatedAt) }}</p>

    <nuxt-content :document="article" />

    <author :author="article.author" />
  </article>
</template>

Теперь мы можем переместить этот компонент из папки global напрямую в папку components, и он будет автоматически импортирован в _slag.vue, поскольку мы используем его в шаблоне.


Добавление блока с кодом в статью


Мы можем стилизовать блок кода с помощью prismJS, который будет автоматически подключаться к посту. Это означает, что мы можем написать наш блок кода, используя правильный markdown синтаксис, и наш блок кода будет отображаться со стилями в зависимости от языка.


```js
export default {
  nuxt: 'is the best'
}
```
```html
<p>code styling is easy</p>
```

В итоге будет выглядеть как:


image


Мы также можем указать имя файла, содержащего данный кусок кода, добавив его в квадратных скобках после языка блока кода.


```js[my-first-blog-post.md]
export default {
  nuxt: 'is the best'
}
```

Мы получим:


image


Прим. пер.: Было бы неплохо сделать такое же для Хабра. Если такая фича уже существует, подскажите в комментах.

Имя файла будет преобразовано в span с классом filename, который мы сможем оформить так, как нам нравится. В этом примере я использую классы tailwind, но вы можете использовать обычный CSS, если хотите.


Код в файле assets/css/tailwind.css:


.nuxt-content-highlight {
  @apply relative;
}
.nuxt-content-highlight .filename {
  @apply absolute right-0 text-gray-600 font-light z-10 mr-2 mt-1 text-sm;
}

Можно использовать другую тему, например prism-themes, мы можем установить ее, а затем добавить её в параметры кофига в nuxt.config.


npm install prism-themes
// or
yarn add prism-themes

Затем в нашем файле nuxt.config в параметрах content мы можем добавить объект markdown с параметром prism и в theme указать тему, которую мы хотим использовать.


content: {
  markdown: {
    prism: {
      theme: 'prism-themes/themes/prism-material-oceanic.css'
    }
  }
}

Создание компонента предыдущей и следующей статьи


Теперь у нас есть полноценный пост в блоге, но было бы здорово, если бы пользователи могли легко переходить от одного поста к другому. Сначала давайте продублируем наш пост, чтобы у нас было 3 поста. Затем давайте создадим новый компонент для наших предыдущих и следующих постов.


touch components/PrevNext.vue

В этом компоненте мы используем v-if внутри нашего компонентаNuxtLink, чтобы узнать, есть ли предыдущая запись в блоге, и если есть, мы добавляем на нее ссылку. Мы можем распечатать заголовок нашей статьи, используя переменные prev и next, поскольку они содержат всю информацию из статьи. Это означает, что мы могли бы создать карточку с изображением и описанием, чтобы показать следующую и предыдущую статью, но для этого примера мы просто отобразим заголовок. Если предыдущего поста нет, мы просто печатаем пустой span, который удобен для стилизации. Затем мы делаем то же самое со следующей ссылкой.


<template>
  <div class="flex justify-between">
    <NuxtLink
      v-if="prev"
      :to="{ name: 'blog-slug', params: { slug: prev.slug } }"
      class="text-primary font-bold hover:underline"
    >
      {{ prev.title }}
    </NuxtLink>
    <span v-else>&nbsp;</span>
    <NuxtLink
      v-if="next"
      :to="{ name: 'blog-slug', params: { slug: next.slug } }"
      class="font-bold hover:underline"
    >
      {{ next.title }}
    </NuxtLink>
    <span v-else>&nbsp;</span>
  </div>
</template>

Мы передаем пропсы prev и next в компонент, чтобы можно было отображать их на странице нашего блога.


<script>
  export default {
    props: {
      prev: {
        type: Object,
        default: () => null
      },
      next: {
        type: Object,
        default: () => null
      }
    }
  }
</script>

Теперь мы можем получить предыдущие и следующие статьи, добавив их в asyncData. Мы создаем массив констант с именами prev и next, которые получим из папки articles с помощью функции $content. На этот раз нам нужны только заголовок и slug(имя .md файла), для этого по цепочке привязываем метод only(), которому передаем 'title' и 'slug'.


Мы можем использовать метод sortBy() для сортировки данных по дате createdAt в возрастающем порядке. Затем мы используем метод surround() и передаем slug из params, чтобы он мог получить правильный slug для предыдущей и следующей публикации.


Затем мы возвращаем prev и next, вместе со статьей article.


async asyncData({ $content, params }) {
    const article = await $content('articles', params.slug).fetch()

    const [prev, next] = await $content('articles')
      .only(['title', 'slug'])
      .sortBy('createdAt', 'asc')
      .surround(params.slug)
      .fetch()

    return {
      article,
      prev,
      next
    }
  },

Теперь мы можем добавить наш компонент <prev-next> на нашу _slag страницу, передав параметры prev и next.


<template>
  <article>
    <h1>{{ article.title }}</h1>
    <p>{{ article.description }}</p>
    <img :src="article.img" :alt="article.alt" />
    <p>Article last updated: {{ formatDate(article.updatedAt) }}</p>

    <nuxt-content :document="article" />

    <author :author="article.author" />

    <prev-next :prev="prev" :next="next" />
  </article>
</template>

Поскольку мы установили components: true в нашем файле nuxt.config, нам не нужно импортировать этот компонент, чтобы его использовать.

Работа с API


При запросе данных модуль Content предоставляет доступ к API, чтобы можно было запрашивать их напрямую и видеть, что возвращается. У нас есть доступ к API в режиме разработки по следующему URL-адресу: http://localhost:3000/_content/. В нашем примере запрос ничего не вернет, так как наши статьи находятся в папке с именем article, поэтому нам нужно использовать этот URL http://localhost:3000/_content/articles, чтобы увидеть список статей.


Мы можем увидеть отдельные статьи, добавив имя файлаhttp://localhost:3000/_content/articles/my-first-blog-post

Вы можете использовать расширение Chrome, например JSON Viewer Awesome, для удобства чтения.

Мы можем запрашивать данные прямо в URL и получать в виде JSON объекта, который затем мы можем использовать для создания главной страницы блога со списком всех статей. Используя API, можно увидеть все доступные данные и решить какие из них нам нужны, в данном случае для главной страницы нам понадобится только заголовок, описание, img, slug и данные автора. Давайте посмотрим, на что это будет похоже.


http://localhost:3000/_content/articles?only=title&only=description&only=img&only=slug&only=author



Список всех статей


Теперь мы можем главную страницу блога, чтобы перечислить все наши статьи. Поскольку у нас уже есть страница index.vue, нам просто нужно удалить весь демонстрационный код на этой странице.


В этом проекте я использовала главную страницу(index.vue в папке pages) вместо создания файла index.vue внутри папки blog, потому что в этом примере у меня нет других страниц, но обычно у вас может быть домашняя страница, страница контактов, а затем страница блога и т. д.

Передавая $content и params из контекста в функцию asyncData, мы затем создаем константу articles, которая ожидает получения асинхронных данных из $content, которой мы передали название папки articles, содержащей все статьи, и параметр slug из params. Затем мы можем использовать only(), чтобы получить только заголовок, описание, img, slug и данные автора, поскольку мы тестировали API, и узнали, что это даст нам именно то, что нам нужно. Мы можем использовать sortBy() для сортировки по дате createdAt, а затем привязываем fetch() и возвращаем статьи.


<script>
  export default {
    async asyncData({ $content, params }) {
      const articles = await $content('articles', params.slug)
        .only(['title', 'description', 'img', 'slug', 'author'])
        .sortBy('createdAt', 'asc')
        .fetch()

      return {
        articles
      }
    }
  }
</script>

Массив статей теперь доступен нам, как любая реактивная переменная, поэтому мы можем использовать его в нашем шаблоне, используя v-for, чтобы перебирать все статьи и распечатывать заголовок статьи, имя автора, описание, дату публикации, дату обновления и изображение в компоненте <NuxtLink> для ссылки на страницу статьи.


<template>
  <div>
    <h1>Blog Posts</h1>
    <ul>
      <li v-for="article of articles" :key="article.slug">
        <NuxtLink :to="{ name: 'blog-slug', params: { slug: article.slug } }">
          <img :src="article.img" />
          <div>
            <h2>{{ article.title }}</h2>
            <p>by {{ article.author.name }}</p>
            <p>{{ article.description }}</p>
          </div>
        </NuxtLink>
      </li>
    </ul>
  </div>
</template>

Использование запроса where для создания страницы автора


Модуль Content дает возможность фильтровать результаты, используя запрос where. У нас может быть страница автора, на которой отображаются данные об авторе и все статьи этого автора.


touch pages/blog/author/_author.vue

Как и раньше, мы используем asyncData для получения данных, но на этот раз мы добавляем метод where(). Мы хотим получать статьи, в которых автор совпадает с именем автора, полученным из params.


Например:


http://localhost:3000/_content/articles?author.name=Maria


Поскольку мы использовали объект для автора, нам нужно добавить nestedProperties в качестве опции к нашему свойству content в нашем файле nuxt.config и передать ему то, что мы хотим запросить (только для запросов с точечной нотацией).


export default {
  content: {
    nestedProperties: ['author.name']
  }
}

Как видим, мы получаем все наши данные только для автора Марии. Если бы мы использовали maria без заглавной буквы, мы бы ничего не получили. Поэтому мы можем использовать $regex, чтобы оно оставалось с заглавной буквы.


Затем мы получаем все детали, которые хотим показать на этой странице. В последнем примере мы использовали метод only() для получения нужных параметров, но поскольку нам требуется довольно много контента, мы можем вместо этого использовать метод without() и передать в него то, что мы не хотим получить в теле сообщения.


<script>
  export default {
    async asyncData({ $content, params }) {
      const articles = await $content('articles', params.slug)
        .where({
          'author.name': {
            $regex: [params.author, 'i']
          }
        })
        .without('body')
        .sortBy('createdAt', 'asc')
        .fetch()

      return {
        articles
      }
    }
  }
</script>

Вы можете использовать массив и передать больше, чем просто тело, методу without():


without(['body', 'title'])

Затем мы можем использовать эти данные, чтобы распечатать красивую страницу автора, показывающую его имя автора, биографию, а также все его посты.


<template>
  <div>
    <h1>Author: {{ articles[0].author.name }}</h1>
    <p>Bio: {{ articles[0].author.bio }}</p>
    <h3>Here are a list of articles by {{ articles[0].author.name }}:</h3>
    <ul>
      <li v-for="article in articles" :key="article.slug">
        <NuxtLink :to="{ name: 'blog-slug', params: { slug: article.slug } }">
          <img :src="article.img" :alt="article.alt" />
          <div>
            <h2>{{ article.title }}</h2>
            <p>{{ article.description }}</p>
            <p>{{ formatDate(article.updatedAt) }}</p>
          </div>
        </NuxtLink>
      </li>
    </ul>
  </div>
</template>

Обратите внимание, что все стили были удалены из этого примера. Вы можете стилизовать страницу самостоятельно или скопировать стили из демонстрационного кода.

Чтобы отформатировать нашу дату, мы можем добавить метод, который создали ранее:


methods: {
    formatDate(date) {
      const options = { year: 'numeric', month: 'long', day: 'numeric' }
      return new Date(date).toLocaleDateString('en', options)
    }
  }

И, конечно же, мы должны сделать ссылку из поста в блоге на страницу данного автора.


<NuxtLink :to="`/blog/author/${author.name}`">
  <img :src="author.img" />
  <div>
    <h4>Author</h4>
    <p>{{ author.name }}</p>
    <p>{{ author.bio }}</p>
  </div>
</NuxtLink>

Добавляем поле поиска


Модуль Nuxt Content дает нам возможность искать в статьях с помощью метода search().


Давайте сначала создадим компонент поиска.


touch components/AppSearchInput.vue

Добавляем свойство data(), которое будет возвращать searchQuery, которая по умолчанию пустая строка, и массив статей, который также пуст. Затем мы используем метод watch из Vue, чтобы наблюдать за значением searchQuery. Если в searchQuery ничего нет, то массив статей пуст, и мы просто вызываем return. В другом случае, мы асинхронно получаем статьи, с помощью $content. Теперь мы можем использовать метод limit(), чтобы ограничить количество возвращаемых результатов, а затем мы используем метод search(), передаем ему searchQuery в качестве аргумента, а затем мы добавляем метод fetch().


<script>
  export default {
    data() {
      return {
        searchQuery: '',
        articles: []
      }
    },
    watch: {
      async searchQuery(searchQuery) {
        if (!searchQuery) {
          this.articles = []
          return
        }
        this.articles = await this.$content('articles')
          .limit(6)
          .search(searchQuery)
          .fetch()
      }
    }
  }
</script>

Затем нам нужно добавить в шаблон input и с помощью v-model мы подключаем его к нашему свойству данных SearchQuery. Затем, если есть результаты поиска, мы используем v-for, чтобы перечислить статьи, используя компонент<NuxtLink>для ссылки на их страницы.


<template>
  <div>
    <input
      v-model="searchQuery"
      type="search"
      autocomplete="off"
      placeholder="Search Articles"
    />
    <ul v-if="articles.length">
      <li v-for="article of articles" :key="article.slug">
        <NuxtLink :to="{ name: 'blog-slug', params: { slug: article.slug } }">
          {{ article.title }}
        </NuxtLink>
      </li>
    </ul>
  </div>
</template>

Теперь мы можем использовать наш компонент где угодно на нашей странице.

<AppSearchInput />

См. код демо для улучшения стиля этой страницы, а также добавленный компонент заголовка, который включает компонент поиска и поэтому отображается на странице автора и заглавной странице.

Редактирование контента в режиме реального времени


Наш блог выглядит великолепно, и если нужно изменить какой-либо контент на странице, мы можем сделать это прямо в браузере благодаря функции редактирования в реальном времени. Все, что вам нужно сделать, это дважды щелкнуть по своей странице в режиме разработки, и откроется окно редактирования. Здесь вы можете изменить любой текст, а также обложку. Вы даже можете добавить компонент, который находится в папке глобальных компонентов, и, просто щелкнув мышью вне окна редактора, вы увидите свои изменения в реальном времени в браузере, а в редакторе и в консоли вы увидите, что файл был изменен и сохранен.



Генерация приложения


Если теперь мы хотим развернуть наш новый замечательный блог, мы можем запустить команду nuxt generate, которая создаст наше приложение, добавив все webpack ресурсы и создав для нас .js бандлы, а затем экспортирует наши html, css, js файлы и изображения как статические ресурсы. Вы также заметите, что нам не нужно было добавлять свойство routes или делать что-либо, чтобы получить новую страницу, поскольку "crawler" будет сканировать все ссылки и генерировать динамические маршруты.


Затем мы можем использовать команду nuxt start, которая будет обслуживать наш готовый к работе статический сайт, чтобы мы могли увидеть его в браузере перед развертыванием.


С nuxt generate, если изменяется только наш контент, это означает, что команда nuxt generate будет экспортировать только статические страницы и не будет проходить через веб-пакет для восстановления сайта, что означает, что наш контент будет обновлен за секунды.


Заключение


Работа с Nuxt Content доставляет огромное удовольствие, и вы тоже сможете сделать и построить гораздо больше. Не забудьте продемонстрировать свои работы на нашем канале в Discord под названием showcase, чтобы мы смогли увидеть интересные вещи, которые вы создали, и, возможно, даже представить их в нашем NuxtLetter. Еще не зарегистрировались? Что ж, сейчас прекрасное время, чтобы зарегистрироваться, поскольку мы продолжаем выпускать новые материалы и функции для Nuxt.js. Наслаждайтесь :)


Дальнейшее изучение


Для получения дополнительной информации о том, как добавить карту сайта в свой блог, ознакомьтесь с этой статьей: Добавление карты сайта с использованием Nuxt Content от Гарета Редферна.

Tags:
Hubs:
Total votes 3: ↑3 and ↓0+3
Comments0

Articles