Разработка сайтов в Антраците, ЛНР. Начало работы с Notion API и его JavaScript SDK

 
 

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

Notion выпустила свой API для всего мира в виде открытой бета-версии. У него отличная документация, к нему действительно легко получить доступ, и, что более важно для нас, разработчиков JavaScript, он также предлагает SDK для JavaScript. 🎉

Хотя для изучения этой статьи не требуется никаких предварительных знаний (я предоставлю все необходимые шаги), мы будем иметь дело с внешним и внутренним кодом, так как потребуется немного Node.js и экспресс-настройки.

Настраивать

Наша установка будет разделена на две части. В первой будут описаны шаги, которые нам необходимо выполнить в программном обеспечении Notion и API. Во втором мы получим код, инициализировав папку, добавив зависимость Notion, создав начальный index.jsи отредактировав, package.jsonчтобы все заработало.

Чтобы продолжить, вам понадобится учетная запись Notion (подробнее об этом ниже), а также последняя копия Node, установленная на вашем компьютере. Как всегда, код руководства можно найти на GitHub.

Настройка понятия

Если у вас еще нет учетной записи Notion, создайте ее, перейдя по этой ссылке. Он имеет очень щедрый бесплатный уровень, и вам не нужно добавлять какую-либо платежную информацию!

После создания учетной записи и входа в систему создайте новую страницу, выбрав Добавить страницу и дав ей имя. Для этого руководства мы выберем Tableбазу данных. Это даст нам пустую таблицу, а это именно то, что нам нужно!

Пустая таблица, которую мы только что создали

Следующим шагом будет создание нескольких столбцов databaseи заполнение их фиктивными данными. В этом руководстве мы будем работать только с полями Nameи Role, как если бы мы работали с базой данных сотрудников компании.

Таблица с новыми полями и фиктивными данными

Теперь перейдем на сайт документации. Вы увидите ссылку Мои интеграции в верхнем углу. Если вы нажмете на нее, вы будете перенаправлены на экран с надписью «Мои интеграции», и ваш, конечно же, будет пустым.

Страница Notion API Мои интеграции

Нажмите «Создать новую интеграцию «, введите свой заголовок и обязательно выберите свой Associated workspace (он будет выбран по умолчанию, но убедитесь в этом). Нажмите «Отправить «, и вы будете перенаправлены на новую страницу с Internal Integration Token (мы будем использовать это в нашем коде) и с двумя полями параметров, где вы хотите использовать свою интеграцию. Вам не нужно ничего делать на этой странице, кроме как скопировать свои tokenи нажать Сохранить изменения.

Примечание: на момент написания не было способа удалить интеграции, поэтому называйте их с умом.

Notion API Создать новую страницу интеграции

Страница интеграции Notion API с токеном

Теперь вернитесь в рабочее пространство Notion. В нашей недавно созданной базе данных мы хотим нажать «Поделиться «, затем «Пригласить «. После этого вы сможете выбрать только что созданную интеграцию. Выберите его и нажмите Invite, и настройка Notion завершена. Отличная работа! 🙌

Рабочее пространство Notion с модальным для интеграции

Настройка кода

Теперь давайте напишем код. Откройте свой терминал и сделайте mkdir notion-api-test (это создаст папку с именем notion-api-test) в выбранном вами месте, а затем войдите в свою папку с помощью cd notion-api-testи выполните npm init -y (эта команда создаст package.jsonс некоторыми базовыми настройками и -yответами флага на некоторые подсказки автоматически, поэтому вы не надо с ними заморачиваться).

Как я упоминал ранее, мы собираемся использовать notion-sdk-js, и для этого нам нужно установить его как зависимость, поэтому мы собираемся сделать npm install @notionhq/client.

Теперь откройте notion-api-testсвой редактор кода, создайте инициал index.jsи rootотредактируйте package.json scripts, заменив то, что там, следующим:

«scripts»: {

«start»: «node index»

},

Давайте также создадим.gitignoreфайл и еще один с именем.env. Позволяет.gitignoreвам помещать внутрь разные имена файлов/папок, а это означает, что эти файлы/папки не будут добавлены в ваш репозиторий, когда вы нажмете свой код. Это очень важно, потому что наш integration token (помните это?) будет внутри.envфайла, вот так:

NOTION_API_KEY = YOUR_TOKEN_HERE

Это означает, что внутри вашего.gitignoreвы должны добавить это в первую строку:

.env

Теперь, когда у нас есть.envфайл, мы также должны добавить новую зависимость, dotenv, чтобы вы могли загрузить свою NOTION_API_KEYпеременную. Вы можете сделать это, выполнив npm install dotenv.

Настройка кода завершена, и ваша папка должна выглядеть примерно так, как показано ниже. 🎉

Как должна выглядеть ваша структура папок

Извлечение данных из Notion API

Теперь, когда скучная часть позади, давайте перейдем к хорошему! Наш index.jsфайл будет файлом Node.js, а следующий блок кода показывает наш начальный код и то, что именно делает каждая строка!

// this will allow us to import our variable

require («dotenv»).config () ;

// the following lines are required to initialize a Notion client

const { Client } = require («@notionhq/client») ;

// this line initializes the Notion Client using our key

const notion = new Client ({ auth: process.env.NOTION_API_KEY }) ;

Здесь нам также понадобится дополнительная вещь, а именно идентификатор базы данных, которую мы создали в нашем рабочем пространстве Notion. Это можно получить из строки URL-адреса браузера. Он идет после имени вашей рабочей области (если она у вас есть) и косой черты (myworkspace/) и перед вопросительным знаком (?). Идентификатор состоит из 32 символов, содержащих цифры и буквы.

https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...

|--------- Database ID --------|

В целях безопасности вы также должны вставить этот идентификатор в свой.envфайл, чтобы он выглядел примерно так:

NOTION_API_KEY = YOUR_TOKEN_HERE

NOTION_API_DATABASE = YOUR_DATABASE_ID_HERE

Затем мы импортируем его в наш index.jsс помощью этого:

const databaseId = process.env.NOTION_API_DATABASE;

Теперь, чтобы убедиться, что наш API работает, давайте создадим функцию, которая обращается к нашей базе данных. Для этого создадим async function:

const getDatabase = async () => {

const response = await notion.databases.query ({ database_id: databaseId }) ;

console.log (response) ;

};

getDatabase () ;

Если вы сейчас запустите npm startв своем терминале, вы должны увидеть журнал objectсо resultsсвойством, которое имеет массив. Этот массив содержит записи в вашей базе данных. Чтобы изучить их, мы можем сделать следующее:

const getDatabase = async () => {

const response = await notion.databases.query ({ database_id: databaseId }) ;

const responseResults = response.results.map ((page) => {

return {

id: page.id,

name: page.properties.Name.title[0]?.plain_text,

role: page.properties.Role.rich_text[0]?.plain_text,

};

}) ;

// this console.log is just so you can see what we’re getting here

console.log (responseResults) ;

return responseResults;

};

Приведенный выше код сопоставляется с нашими results (соответствует записям в нашей базе данных), и мы сопоставляем пути для различных свойств с именами, которые мы выбираем (в данном случае id, nameи role). Обратите внимание, насколько конкретен путь к объекту. Я использовал необязательную цепочку для учета пустых строк в базе данных или строк, в которых одно или другое из этих полей не заполнено.

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

Если изучение каждого свойства и его использование console.log () не для вас, вы всегда можете использовать Postman для проверки ответа. К сожалению, это не входит в рамки этого руководства, но вы можете проверить пост «Как освоить рабочий процесс API с помощью Postman «, чтобы попробовать!

Еще одно важное замечание: обратите внимание на notion.databases.queryто, что мы использовали. Если вы посмотрите документацию Notion API, вы увидите, что мы используем POST | Query a database. Мы могли бы использовать просто GET | Retrieve a database, но здесь я хотел бы призвать вас прочитать документацию и попробовать отсортировать список по-другому!

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

exports.getDatabase = async function () {

const response = await notion.databases.query ({ database_id: databaseId }) ;

const responseResults = response.results.map ((page) => {

return {

id: page.id,

name: page.properties.Name.title[0]?.plain_text,

role: page.properties.Role.rich_text[0]?.plain_text,

};

}) ;

return responseResults;

};

Настройка экспресс-сервера

Выполнив предыдущий шаг, мы теперь можем успешно получить наши результаты. Но чтобы иметь возможность использовать их правильно, нам нужно создать сервер, и самый простой способ сделать это — поскольку мы используем Node.js — использовать Express. Итак, мы начнем с запуска npm install expressи создания нового файла в корне с именем server.js.

Если expressсмущает вас, не волнуйтесь. Мы будем использовать его для облегчения нашей работы и быстрого создания серверной части и сервера для нашего приложения. Без него мы не смогли бы правильно получить наши данные, поскольку мы инициализируем наш клиент Notion в нашем коде.

В нашем server.jsфайле мы начнем с импорта expressмодуля, в котором у нас есть наш код (index.js), наша getDatabaseфункция, номер порта и переменная для инициализации нашей expressфункции:

const express = require («express») ;

// our module

const moduleToFetch = require («. /index») ;

// our function

const getDatabase = moduleToFetch.getDatabase;

const port = 8000;

const app = express () ;

// this last command will log a message on your terminal when you do `npm start`

app.listen (port, console.log (`Server started on ${port}`));

Поскольку сейчас мы импортируем наш код в новый файл, server.jsмы должны изменить нашу startкоманду на package.jsonпоиск server, поэтому она должна выглядеть так:

«scripts»: {

«start»: «node server»

},

Если вы сейчас запустите npm start, вы увидите Server started on 8000сообщение, которое означает, что наша установка работает должным образом! Отличная работа!

Теперь, когда наше expressприложение работает, нам нужно заставить нашу базу данных работать с ним, и мы можем сделать это с помощью app.get (). Этому методу нужен путь (в нашем случае это не имеет значения) и функция обратного вызова (которая будет вызывать нашу getDatabaseфункцию):

app.get («/users», async (req, res) => {

const users = await getDatabase () ;

res.json (users) ;

}) ;

Приведенный выше код использует указанный app.getметод, и внутри нашей функции обратного вызова мы получаем результаты от нашей функции, и мы используем.json () функцию промежуточного программного обеспечения Express, которая анализирует запрос в читаемые и рабочие данные. (Вы можете узнать об этом немного больше в официальной документации.)

Это означает, что мы успешно получили доступ к нашим данным и создали маршрут для их «выборки». В качестве последнего шага мы должны добавить app.use (express.static («public»)); в наш server.jsфайл, чтобы конечный результат выглядел примерно так:

const express = require («express») ;

// our module

const moduleToFetch = require («. /index») ;

// our function

const getDatabase = moduleToFetch.getDatabase;

const port = 8000;

const app = express () ;

// the code line we just added

app.use (express.static («public»));

app.get («/users», async (req, res) => {

const users = await getDatabase () ;

res.json (users) ;

}) ;

app.listen (port, console.log (`Server started on ${port}`));

Этот последний фрагмент кода сообщает нашему бэкенду useконкретную папку, в которой мы создадим наш внешний код, которая будет publicпапкой. Здесь мы будем работать с нашим HTML, CSS и JavaScript, чтобы получить доступ к этому /usersмаршруту, который мы создали в нашей серверной части. Давайте приступим!

Отображение данных из Notion API

Мы начнем с создания в корне нашего проекта папки с именем public. Здесь будет жить наш интерфейсный код.

Части HTML и CSS просты, поэтому я в основном просто оставлю код здесь и сосредоточусь на части JavaScript, поскольку именно для этого мы все здесь!

Наш HTML (/public/index.html) будет выглядеть так:

<! DOCTYPE html>

 

 

http-equiv="X-UA-Compatible" content="IE=edge" />

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

И наш CSS (/public/style.css) будет выглядеть так:

body,

html {

padding: 0;

margin: 0;

height: 100vh;

width: 100vw;

font-family: Arial, Helvetica, sans-serif;

position: relative;

}

#banner {

height: 50px;

display: flex;

justify-content: center;

align-items: center;

background-color: #ef4444;

color: white;

font-weight: bold;

}

#wrapper {

display: flex;

justify-content: center;

align-items: center;

height: calc (100vh — 50px) ;

}

#container {

width: 80vw;

margin: auto;

display: grid;

grid-template-columns: repeat (auto-fit, minmax (250px, 1fr));

grid-auto-rows: 200px;

gap: 20px;

}

.userContainer {

display: flex;

flex-direction: column;

justify-content: center;

align-items: center;

box-shadow: rgba (149, 157, 165, 0.2) 0px 8px 24px;

border-radius: 10px;

}

Если вы сейчас запустите npm startсвой проект и посетите http: //localhost:8000, вы должны увидеть свой интерфейсный код.

Пустая HTML-страница с баннером вверху

Теперь наш public/main.jsфайл! Наш первый шаг — сделать запрос к маршруту, который мы создали на серверной части (/users/), что позволит нам получить информацию из нашей базы данных:

const getDataFromBackend = async () => {

const rest = await fetch («http: //localhost:8000/users») ;

const data = await rest.json () ;

return data;

};

// Note that top-level await is only available in modern browsers

// https://caniuse.com/mdn-javascript_operators_await_top_level

const res = await getDataFromBackend () ;

console.log (res) ;

Когда вы зарегистрируете возвращаемое значение этой функции, вы увидите в своих инструментах разработчика ту же информацию, которую мы раньше могли видеть только на терминале, а это означает, что теперь мы можем использовать наши данные на внешнем интерфейсе! Молодец! 🎉

console.log () данных внутри getDataFromBackend ()

Давайте теперь покажем эти данные внутри нашего

 

, что будет очень просто. Мы начнем с того, что getElementByIdполучим нужный элемент, а затем создадим функцию, которая будет выполняться getDataFromBackend () и будет перебирать каждый объект внутри нашего dataмассива и передавать это содержимое в наш HTML. Вот мой подход к этому:

 

// Add data to HTML

const addData = async () => {

const data = await getDataFromBackend () ;

data.forEach ((value) => {

const div = document.createElement («div») ;

div.classList.add («userContainer») ;

div.innerHTML = `

${value.name}

${value.role}

`;

container.append (div) ;

}) ;

};

addData () ;

Итак, еще раз, наша dataпеременная (внутри addDataфункции) представляет собой ту же информацию, которую мы могли бы видеть в журнале (массив объектов), и мы перебираем ее, создавая

класс с классом userContainer, а внутри него у нас есть nameи roleдля каждая из наших записей в базе данных.

 

Если вы сейчас запустите свой код, вы должны увидеть что-то вроде того, что изображено ниже!

Что вы должны увидеть при отображении данных в вашем HTML

Запись данных в нашу базу данных понятий

Это было бы отличной точкой остановки для наших экспериментов с Notion API, но мы можем сделать еще больше! Давайте теперь добавим новые записи в нашу базу данных с помощью Create Pageзапроса POST (который можно найти здесь), чтобы у нас было полностью функционирующее и работающее приложение, использующее практически все возможности Notion API.

Итак, наша идея здесь будет заключаться в том, чтобы добавить форму в наш интерфейс, которая после заполнения и отправки будет отправлять новые данные в нашу базу данных, которые затем будут отображаться в нашем интерфейсе!

Давайте начнем с добавления новой функции в наш index.jsвызываемый файл newEntryToDatabase. Учитывая документацию, теперь мы должны сделать const response = await notion.pages.create () и передать объект, соответствующий текущей базе данных, над которой мы работаем. Он также будет иметь два аргумента nameи role, которые для этого проекта будут выглядеть так:

exports.newEntryToDatabase = async function (name, role) {

const response = await notion.pages.create ({

parent: {

database_id: process.env.NOTION_API_DATABASE,

},

properties: {

Name: {

title: [

{

text: {

content: name,

},

},

],

},

Role: {

rich_text: [

{

text: {

content: role,

},

},

],

},

},

}) ;

return response;

};

Обратите внимание, что мы делаем с этим объектом. По сути, мы делаем то же самое, что и getDatabaseс нашей responseResultsпеременной, проходя через каждое свойство, пока не доберемся до свойства, с которым действительно хотим работать. Здесь мы используем наши аргументы в качестве значений свойств. Если это сбивает с толку, ничего страшного; просмотрите связанную документацию в этом разделе, чтобы увидеть больше примеров!

Теперь, переходя к нашему server.js, давайте не забудем импортировать нашу новую функцию с const newEntryToDatabase = moduleToFetch.newEntryToDatabase; вверху файла. Мы также сделаем POSTзапрос, используя app.post (). Здесь нам также нужен маршрут (это будет /submit-form), и наша функция обратного вызова должна получить nameи roleиз запроса (наши заполненные поля формы) и вызвать newEntryToDatabaseс этими двумя аргументами. Затем мы завершаем нашу функцию перенаправлением на наш базовый маршрут, /а также завершаем наш запрос.

Нашему server.jsфайлу также потребуется немного кода внутри app.use () функции, которая представляет собой файл express.urlencoded. Это промежуточное ПО для Express, поэтому мы можем использовать POSTзапрос, так как мы фактически отправляем данные:

const express = require («express») ;

const moduleToFetch = require («. /index») ;

const getDatabase = moduleToFetch.getDatabase;

// importing our function

const newEntryToDatabase = moduleToFetch.newEntryToDatabase;

const port = 8000;

const app = express () ;

app.use (express.static («public»));

app.use (

express.urlencoded ({

extended: true,

})

) ;

app.get («/users», async (req, res) => {

const users = await getDatabase () ;

res.json (users) ;

}) ;

// our newly added bit of code

app.post («/submit-form», async (req, res) => {

const name = req.body.name;

const role = req.body.role;

await newEntryToDatabase (name, role) ;

res.redirect («/») ;

res.end () ;

}) ;

app.listen (port, console.log (`Server started on ${port}`));

Теперь наша серверная часть готова, и нам нужно поработать над кодом внешнего интерфейса. На этом этапе вам следует перезапустить сервер Express, чтобы он распознал изменения.

Справедливости ради, единственное, что вам нужно в коде внешнего интерфейса, — это a

в вашем HTML с method="POST"и action="/submit-form". Это в основном говорит нашему коду, какой тип формы должен быть, а также связывает его с маршрутом (/submit-form), который мы создали для обработки запросов.

 

Так что-то вроде следующего было бы более чем достаточно:

 

 

 

Если мы заполним поля, отправим нашу форму и перезагрузим страницу, мы увидим новую запись, и если мы войдем в нашу рабочую область Notion, мы увидим там запись. Функциональность завершена. Отличная работа! 🎉

Но чтобы улучшить наш интерфейс, идея здесь в том, что у нас будет a button, который при нажатии будет открывать модальное окно с form (также с возможностью закрыть его без заполнения), так что вот мой HTML:

 

 

 

 

 

 

 

 

 

 

 

 

Add a new user to your database

 

<input

type="text"

name="name"

placeholder="Insert user name"

class="inputField"

required

/>

<input

type="text"

name="role"

placeholder="Insert user role"

class="inputField"

required

/>

 

 

 

 

И вот CSS, который должен его сопровождать:

/* The rest of the code above */

#newUserButton {

position: absolute;

bottom: 10px;

right: 10px;

padding: 10px 20px;

background-color: #ef4444;

color: white;

font-weight: bold;

border: none;

border-radius: 4px;

}

#addUserFormContainer {

position: absolute;

top: 0;

left: 0;

height: 100vh;

width: 100vw;

display: none;

flex-direction: column;

justify-content: center;

align-items: center;

background: rgba (255, 255, 255, 0.4) ;

backdrop-filter: blur (20px) ;

}

#closeFormButton {

position: absolute;

top: 10px;

right: 10px;

padding: 10px 20px;

background-color: black;

color: white;

font-weight: bold;

border: none;

border-radius: 4px;

}

#formTitle {

margin-bottom: 40px;

}

#addUserForm {

padding: 50px 100px;

width: 300px;

display: flex;

flex-direction: column;

justify-content: center;

align-items: center;

background: white;

box-shadow: rgba (149, 157, 165, 0.2) 0px 8px 24px;

}

#addUserForm input {

width: 100%;

box-sizing: border-box;

}

.inputField {

margin-bottom: 20px;

padding: 10px 20px;

border: 1px solid #b3b3b3;

border-radius: 4px;

}

#submitFormInput {

padding: 10px 20px;

margin-bottom: none;

background-color: #ef4444;

color: white;

font-weight: bold;

border: 1px solid #ef4444;

border-radius: 4px;

}

Если вы сейчас посетите свою страницу, вы увидите только красную кнопку без какой-либо реальной пользы, поэтому теперь нам нужно поработать над нашим JavaScript. Поэтому давайте перейдем к нашему /public/main.jsфайлу!

Здесь мы начнем с захвата #newUserButton, #closeFormButtonи #addUserFormContainer:

const container = document.getElementById («container») ;

// the new variables

const openFormButton = document.getElementById («newUserButton») ;

const closeFormButton = document.getElementById («closeFormButton») ;

const addUserFormContainer = document.getElementById («addUserFormContainer») ;

Теперь openFormButtonмы добавим clickпрослушиватель событий, который в конечном итоге будет стилизовать наш addUserFormContainerс помощью display: flex:

openFormButton.addEventListener («click», () => {

addUserFormContainer.style.display = «flex»;

}) ;

Теперь, если вы нажмете кнопку «Добавить нового пользователя «, откроется форма.

Чтобы закрыть наше formмодальное окно, нам просто нужно удалить то flex, что мы добавляем, нажав closeFormButton, так что это должно выглядеть так:

closeFormButton.addEventListener («click», () => {

addUserFormContainer.style.display = «none»;

}) ;

И... мы закончили! Теперь, когда вы вводите имя и роль в форму, они будут добавлены в вашу базу данных Notion и будут отображаться во внешнем интерфейсе приложения.

Мы только что создали полностью функционирующий веб-сайт, который получает базу данных, обрабатывает данные, отображает их, а также позволяет вам добавлять к ним! Разве это не невероятно?

Вот короткая видеодемонстрация готового результата.

Двигаясь дальше

Хотя эта демонстрация демонстрирует некоторые важные возможности использования Notion API, в нашем приложении еще есть возможности для улучшения. Например, было бы целесообразно реализовать некоторую обработку ошибок или счетчик загрузки, показывающий, когда приложение взаимодействует с Notion (и, следовательно, не отвечает). Кроме того, вместо того, чтобы постоянно запрашивать API для извлечения данных, вы можете довольно легко превратить это в одностраничное приложение, которое один раз запрашивает API, а затем сохраняет данные, с которыми мы работаем, в состоянии.

Если вам нужна помощь в реализации чего-либо из этого или вы хотите продемонстрировать свое решение, почему бы не зайти на форумы SitePoint и не сообщить нам об этом.

Заключение

В этом проекте мы изучили почти все функциональные возможности Notion API, и я думаю, стало ясно, насколько удивительным он может быть на самом деле!

Я надеюсь, что этот пост дал вам полное представление об API Notion и вдохновил вас на создание с его помощью большего количества вещей!

Если вы хотите быстро протестировать этот проект, вы можете клонировать его из нашего репозитория GitHub.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая