This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { bindMarkdownToolbar } from '../features/markdown-editor/formatting.js';
|
||||
import { estimateReadingMinutes } from '../shared/lib/markdown.js';
|
||||
import { slugify } from '../shared/lib/slug.js';
|
||||
|
||||
export function bindEvents(context) {
|
||||
context.form.addEventListener('input', () => {
|
||||
context.setDirty(true);
|
||||
context.renderPreview();
|
||||
});
|
||||
|
||||
context.searchInput.addEventListener('input', context.renderArticles);
|
||||
context.newButton.addEventListener('click', context.createNewArticle);
|
||||
context.duplicateButton.addEventListener('click', context.duplicateArticle);
|
||||
context.saveButton.addEventListener('click', context.saveArticle);
|
||||
context.deleteButton.addEventListener('click', context.deleteArticle);
|
||||
context.coverUploadButton.addEventListener('click', () => context.coverUploadInput.click());
|
||||
context.coverUploadInput.addEventListener('change', context.uploadCover);
|
||||
context.assetUploadButton.addEventListener('click', () => context.assetUploadInput.click());
|
||||
context.assetUploadInput.addEventListener('change', context.uploadMarkdownAssets);
|
||||
|
||||
bindMarkdownToolbar({
|
||||
field: context.fields.contentMarkdown,
|
||||
onChange() {
|
||||
context.setDirty(true);
|
||||
context.renderPreview();
|
||||
},
|
||||
});
|
||||
|
||||
context.slugButton.addEventListener('click', () => {
|
||||
context.fields.slug.value = slugify(context.fields.title.value);
|
||||
context.setDirty(true);
|
||||
});
|
||||
|
||||
context.minutesButton.addEventListener('click', () => {
|
||||
context.fields.readingMinutes.value = estimateReadingMinutes(context.fields.contentMarkdown.value);
|
||||
context.setDirty(true);
|
||||
});
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { getArticles } from '../entities/article/api.js';
|
||||
|
||||
export async function bootstrap(context) {
|
||||
context.setStatus('Загрузка данных...');
|
||||
|
||||
try {
|
||||
const [articles] = await Promise.all([getArticles(), context.loadAssets()]);
|
||||
context.state.articles = articles;
|
||||
context.renderArticles();
|
||||
context.selectArticle(context.state.articles[0]?.slug || null);
|
||||
context.setStatus(`Загружено ${context.state.articles.length} статей`);
|
||||
} catch (error) {
|
||||
context.setStatus(error.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { getAssets } from '../entities/asset/api.js';
|
||||
import { deleteArticle } from '../features/article-actions/deleteArticle.js';
|
||||
import { duplicateArticle } from '../features/article-actions/duplicateArticle.js';
|
||||
import { createNewArticle } from '../features/article-actions/createNewArticle.js';
|
||||
import { reloadArticles } from '../features/article-actions/reloadArticles.js';
|
||||
import { renderArticles } from '../features/article-actions/renderArticles.js';
|
||||
import { saveArticle } from '../features/article-actions/saveArticle.js';
|
||||
import { selectArticle } from '../features/article-actions/selectArticle.js';
|
||||
import { updateArticleButtons } from '../features/article-actions/buttons.js';
|
||||
import { mergeAssetPaths } from '../features/asset-picker/mergeAssets.js';
|
||||
import { renderAssetOptions } from '../features/asset-picker/renderAssetOptions.js';
|
||||
import { uploadCover } from '../features/asset-upload/uploadCover.js';
|
||||
import { uploadMarkdownAssets } from '../features/asset-upload/uploadMarkdownAssets.js';
|
||||
import { renderPreview } from '../features/preview-panel/renderPreview.js';
|
||||
import { confirmDiscard, setDirty } from '../features/session-state/dirtyState.js';
|
||||
import { setStatus } from '../features/session-state/status.js';
|
||||
import { createAdminState } from './model/createAdminState.js';
|
||||
|
||||
export function createAdminContext(dom) {
|
||||
const context = {
|
||||
...dom,
|
||||
state: createAdminState(),
|
||||
};
|
||||
|
||||
context.confirmDiscard = () => confirmDiscard(context.state);
|
||||
context.deleteArticle = () => deleteArticle(context);
|
||||
context.duplicateArticle = () => duplicateArticle(context);
|
||||
context.createNewArticle = () => createNewArticle(context);
|
||||
context.loadAssets = async () => {
|
||||
context.state.assets = await getAssets();
|
||||
context.renderAssetOptions();
|
||||
};
|
||||
context.mergeUploadedAssets = (uploaded) => {
|
||||
context.state.assets = mergeAssetPaths(context.state.assets, uploaded);
|
||||
context.renderAssetOptions();
|
||||
};
|
||||
context.reloadArticles = (selectedSlug) => reloadArticles(context, selectedSlug);
|
||||
context.renderArticles = () => renderArticles(context, context.selectArticle);
|
||||
context.renderAssetOptions = () => renderAssetOptions(context.assetOptions, context.state.assets);
|
||||
context.renderPreview = () => renderPreview(context);
|
||||
context.saveArticle = () => saveArticle(context);
|
||||
context.selectArticle = (slug, options) => selectArticle(slug, context, options);
|
||||
context.setDirty = (dirty) => setDirty({ dirty, saveButton: context.saveButton, state: context.state });
|
||||
context.setStatus = (message) => setStatus(context.statusText, message);
|
||||
context.updateButtons = () => updateArticleButtons(context);
|
||||
context.uploadCover = () => uploadCover(context);
|
||||
context.uploadMarkdownAssets = () => uploadMarkdownAssets(context);
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
export function getAdminDom() {
|
||||
const form = document.querySelector('#article-form');
|
||||
|
||||
return {
|
||||
articleList: document.querySelector('#article-list'),
|
||||
assetOptions: document.querySelector('#asset-options'),
|
||||
assetUploadButton: document.querySelector('#asset-upload-button'),
|
||||
assetUploadInput: document.querySelector('#asset-upload-input'),
|
||||
contentPreview: document.querySelector('#content-preview'),
|
||||
coverPreview: document.querySelector('#cover-preview'),
|
||||
coverUploadButton: document.querySelector('#cover-upload-button'),
|
||||
coverUploadInput: document.querySelector('#cover-upload-input'),
|
||||
deleteButton: document.querySelector('#delete-button'),
|
||||
duplicateButton: document.querySelector('#duplicate-button'),
|
||||
form,
|
||||
fields: getArticleFields(form),
|
||||
minutesButton: document.querySelector('#minutes-button'),
|
||||
newButton: document.querySelector('#new-button'),
|
||||
openArticleLink: document.querySelector('#open-article-link'),
|
||||
saveButton: document.querySelector('#save-button'),
|
||||
searchInput: document.querySelector('#search-input'),
|
||||
slugButton: document.querySelector('#slug-button'),
|
||||
statusText: document.querySelector('#status-text'),
|
||||
};
|
||||
}
|
||||
|
||||
function getArticleFields(form) {
|
||||
return {
|
||||
author: form.elements.author,
|
||||
categories: form.elements.categories,
|
||||
contentMarkdown: form.elements.contentMarkdown,
|
||||
cover: form.elements.cover,
|
||||
date: form.elements.date,
|
||||
excerpt: form.elements.excerpt,
|
||||
readingMinutes: form.elements.readingMinutes,
|
||||
slug: form.elements.slug,
|
||||
title: form.elements.title,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { protectUnsavedChanges } from '../features/session-state/dirtyState.js';
|
||||
import { bootstrap } from './bootstrap.js';
|
||||
import { bindEvents } from './bindEvents.js';
|
||||
import { createAdminContext } from './createAdminContext.js';
|
||||
import { getAdminDom } from './dom.js';
|
||||
|
||||
const context = createAdminContext(getAdminDom());
|
||||
|
||||
bindEvents(context);
|
||||
protectUnsavedChanges(context.state);
|
||||
await bootstrap(context);
|
||||
@@ -0,0 +1,9 @@
|
||||
export function createAdminState() {
|
||||
return {
|
||||
articles: [],
|
||||
assets: [],
|
||||
current: null,
|
||||
dirty: false,
|
||||
selectedSlug: null,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user