1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-31 19:30:40 +02:00

Posts finished

This commit is contained in:
trendschau
2023-07-17 20:47:50 +02:00
parent d0772caf86
commit 1c7dbf36ed
20 changed files with 521 additions and 186 deletions

View File

@@ -481,6 +481,124 @@ class ControllerApiAuthorArticle extends Controller
return $response->withHeader('Content-Type', 'application/json');
}
public function createPost(Request $request, Response $response, $args)
{
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'mycontent', 'create');
if(!$validRights)
{
$response->getBody()->write(json_encode([
'message' => 'You do not have enough rights.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
$params = $request->getParsedBody();
$validate = new Validation();
$validInput = $validate->navigationItem($params);
if($validInput !== true)
{
$errors = $validate->returnFirstValidationErrors($validInput);
$response->getBody()->write(json_encode([
'message' => reset($errors),
'errors' => $errors
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
# set variables
$urlinfo = $this->c->get('urlinfo');
$langattr = $this->settings['langattr'] ?? 'en';
# get navigation
$navigation = new Navigation();
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr);
if($params['folder_id'] == 'root')
{
$folderContent = $draftNavigation;
}
else
{
# get the ids (key path) for item, old folder and new folder
$folderKeyPath = explode('.', $params['folder_id']);
# get the item from structure
$folder = $navigation->getItemWithKeyPath($draftNavigation, $folderKeyPath);
if(!$folder)
{
$response->getBody()->write(json_encode([
'message' => 'We could not find this page. Please refresh and try again.'
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
$folderContent = $folder->folderContent;
}
$slug = Slug::createSlug($params['item_name'], $langattr);
# iterate through the whole content of the new folder
$index = 0;
$writeError = false;
$folderPath = isset($folder) ? $folder->path : '';
$storage = new StorageWrapper('\Typemill\Models\Storage');
foreach($folderContent as $folderItem)
{
# check, if the same name as new item, then return an error
if($folderItem->slug == $slug)
{
$response->getBody()->write(json_encode([
'message' => 'There is already a page with this name. Please choose another name.'
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
}
# add prefix date to the name
$namePath = date("YmdHi") . '-' . $slug;
# create default content
$content = json_encode(['# ' . $params['item_name'], 'Content']);
if($params['type'] == 'file')
{
if(!$storage->writeFile('contentFolder', $folderPath, $namePath . '.txt', $content))
{
$response->getBody()->write(json_encode([
'message' => 'We could not create the file. Please refresh the page and check, if all folders and files are writable.'
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
$storage->updateYaml('contentFolder', $folderPath, $namePath . '.yaml', ['meta' => ['navtitle' => $params['item_name']]]);
}
elseif($params['type'] == 'folder')
{
$response->getBody()->write(json_encode([
'message' => 'We cannot create a folder, only files.'
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
$navigation->clearNavigation();
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr);
$item = $navigation->getItemWithKeyPath($draftNavigation, $folderKeyPath);
$response->getBody()->write(json_encode([
'navigation' => $draftNavigation,
'item' => $item,
]));
return $response->withHeader('Content-Type', 'application/json');
}
public function renameArticle(Request $request, Response $response, $args)
{
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'mycontent', 'edit');

View File

@@ -50,7 +50,7 @@ class ControllerApiAuthorMeta extends Controller
# if item is a folder
if($item->elementType == "folder" && isset($item->contains))
{
$metadata['meta']['contains'] = isset($pagemeta['meta']['contains']) ? $pagemeta['meta']['contains'] : $item->contains;
$metadata['meta']['contains'] = isset($metadata['meta']['contains']) ? $metadata['meta']['contains'] : $item->contains;
# get global metadefinitions
$metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = true);
@@ -237,7 +237,7 @@ class ControllerApiAuthorMeta extends Controller
# if folder has changed and contains pages instead of posts or posts instead of pages
if($item->elementType == "folder" && isset($params['data']['contains']) && isset($pageMeta['meta']['contains']) && $this->hasChanged($params['data'], $pageMeta['meta'], 'contains'))
{
{
if($meta->folderContainsFolders($item))
{
$response->getBody()->write(json_encode([

View File

@@ -166,6 +166,43 @@ class Meta
return $meta;
}
public function folderContainsFolders($folder)
{
foreach($folder->folderContent as $page)
{
if($page->elementType == 'folder')
{
return true;
}
}
return false;
}
# just route it to storageWrapper because wrapper is initialized here and we dont want to initialize it in controllers
public function transformPostsToPages($folder)
{
if($this->storage->transformPostsToPages($folder))
{
return true;
}
# return $this->storage->getError();
return false;
}
public function transformPagesToPosts($folder)
{
if($this->storage->transformPagesToPosts($folder))
{
return true;
}
# return $this->storage->getError();
return false;
}
@@ -181,6 +218,8 @@ class Meta
public function getNavtitle($url)
{
die("meta moddel this method is outdated");
# get the extended structure where the navigation title is stored
$extended = $this->getYaml('cache', 'structure-extended.yaml');
@@ -194,6 +233,9 @@ class Meta
# used by articleApiController and pageController to add title and description if an article is published
public function completePageMeta($content, $settings, $item)
{
die("meta moddel this method is outdated");
$meta = $this->getPageMeta($settings, $item);
if(!$meta)
@@ -237,6 +279,9 @@ class Meta
private function whitelistMeta($meta, $metascheme)
{
die("meta moddel this method is outdated");
# we have only 2 dimensions, so no recursive needed
foreach($meta as $tab => $values)
{
@@ -257,6 +302,8 @@ class Meta
public function generateDescription($content, $parsedown, $item)
{
die("meta moddel this method is outdated");
$description = isset($content[1]) ? $content[1] : '';
# create description or abstract from content
@@ -282,123 +329,4 @@ class Meta
}
return $description;
}
public function transformPagesToPosts($folder)
{
$filetypes = array('md', 'txt', 'yaml');
$result = true;
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
# set default date
$date = date('Y-m-d', time());
$time = date('H-i', time());
$meta = $this->getYaml('content', $page->pathWithoutType . '.yaml');
if($meta)
{
# get dates from meta
if(isset($meta['meta']['manualdate'])){ $date = $meta['meta']['manualdate']; }
elseif(isset($meta['meta']['created'])){ $date = $meta['meta']['created']; }
elseif(isset($meta['meta']['modified'])){ $date = $meta['meta']['modified']; }
# set time
if(isset($meta['meta']['time']))
{
$time = $meta['meta']['time'];
}
}
$datetime = $date . '-' . $time;
$datetime = implode(explode('-', $datetime));
$datetime = substr($datetime,0,12);
# create new file-name without filetype
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $datetime . '-' . $page->slug;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
}
return $result;
}
public function transformPostsToPages($folder)
{
$filetypes = array('md', 'txt', 'yaml');
$index = 0;
$result = true;
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
$order = $index;
if($index < 10)
{
$order = '0' . $index;
}
# create new file-name without filetype
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $order . '-' . $page->slug;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
$index++;
}
return $result;
}
public function folderContainsFolders($folder)
{
foreach($folder->folderContent as $page)
{
if($page->elementType == 'folder')
{
return true;
}
}
return false;
}
}

View File

@@ -633,6 +633,150 @@ class Storage
return $filelist;
}
public function deleteMediaFile($name)
{
# validate name
$name = basename($name);
if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name))
{
return true;
}
return false;
}
public function deleteFileWithName($name)
{
# e.g. delete $name = 'logo';
$name = basename($name);
if($name != '' && !in_array($name, array(".","..")))
{
foreach(glob($this->fileFolder . $name) as $file)
{
unlink($file);
}
}
}
##################
## POST PAGES ##
##################
public function transformPagesToPosts($folder)
{
$filetypes = array('md', 'txt', 'yaml');
$result = true;
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->contentFolder . $page->pathWithoutType;
# set default date
$date = date('Y-m-d', time());
$time = date('H-i', time());
$meta = $this->getYaml('contentFolder', '', $page->pathWithoutType . '.yaml');
if($meta)
{
# get dates from meta
if(isset($meta['meta']['manualdate'])){ $date = $meta['meta']['manualdate']; }
elseif(isset($meta['meta']['created'])){ $date = $meta['meta']['created']; }
elseif(isset($meta['meta']['modified'])){ $date = $meta['meta']['modified']; }
# set time
if(isset($meta['meta']['time']))
{
$time = $meta['meta']['time'];
}
}
$datetime = $date . '-' . $time;
$datetime = implode(explode('-', $datetime));
$datetime = substr($datetime,0,12);
# create new file-name without filetype
$newFile = $this->contentFolder . $folder->path . DIRECTORY_SEPARATOR . $datetime . '-' . $page->slug;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$this->error = "could not rename $oldFilePath to $newFilePath";
$result = false;
}
}
}
}
return $result;
}
public function transformPostsToPages($folder)
{
$filetypes = array('md', 'txt', 'yaml');
$index = 0;
$result = true;
foreach($folder->folderContent as $page)
{
# create old filename without filetype
$oldFile = $this->contentFolder . $page->pathWithoutType;
$order = $index;
if($index < 10)
{
$order = '0' . $index;
}
# create new file-name without filetype
$newFile = $this->contentFolder . $folder->path . DIRECTORY_SEPARATOR . $order . '-' . $page->slug;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldFile . '.' . $filetype;
$newFilePath = $newFile . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$this->error = "could not rename $oldFilePath to $newFilePath";
$result = false;
}
}
}
$index++;
}
return $result;
}
/*
public function getFileDetailsBREAK($name)
{
$name = basename($name);
@@ -653,44 +797,6 @@ class Storage
return false;
}
public function deleteMediaFile($name)
{
# validate name
$name = basename($name);
if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name))
{
return true;
}
return false;
}
public function deleteFileWithName($name)
{
# e.g. delete $name = 'logo';
$name = basename($name);
if($name != '' && !in_array($name, array(".","..")))
{
foreach(glob($this->fileFolder . $name) as $file)
{
unlink($file);
}
}
}
/*
public function getStorageInfoBREAK($item)
{
if(isset($this->$item))

View File

@@ -27,6 +27,8 @@
<div id="meta"></div>
<div id="editor"></div>
<div id="posts"></div>
<div id="publisher" class="fixed bottom-0 w-54rem bg-stone-100 border-t border-stone-200 shadow-md" v-cloak></div>
@@ -41,6 +43,7 @@
<script src="{{ base_url() }}/system/typemill/author/js/vue-meta.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-forms.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-publisher.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-posts.js?v={{ settings.version }}"></script>
<script>
bloxeditor.config.globalProperties.$filters = translatefilter;
@@ -51,6 +54,9 @@
publisher.config.globalProperties.$filters = translatefilter;
publisher.mount('#publisher');
posts.config.globalProperties.$filters = translatefilter;
posts.mount('#posts');
</script>

View File

@@ -714,10 +714,6 @@ video {
bottom: 0.75rem;
}
.top-12 {
top: 3rem;
}
.top-10 {
top: 2.5rem;
}
@@ -734,6 +730,10 @@ video {
z-index: 50;
}
.float-right {
float: right;
}
.m-0 {
margin: 0px;
}

View File

@@ -164,7 +164,7 @@ navigation.component('navilevel',{
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<div v-if="element.elementType == 'folder'" class=" p-1 bg-transparent absolute right-0" @click="callToggle(element.name)">
<div v-if="element.elementType == 'folder' && element.contains == 'pages'" class=" p-1 bg-transparent absolute right-0" @click="callToggle(element.name)">
<svg v-if="isExpanded(element.name)" class="icon icon-cheveron-up">
<use xlink:href="#icon-cheveron-up"></use>
</svg>
@@ -173,7 +173,7 @@ navigation.component('navilevel',{
</svg>
</div>
</div>
<navilevel v-show="isExpanded(element.name)" v-if="element.elementType == 'folder'" :list="element.folderContent" :navigation="element.folderContent" :parentId="element.keyPath" :expanded="expanded" />
<navilevel v-show="isExpanded(element.name)" v-if="element.elementType == 'folder' && element.contains == 'pages'" :list="element.folderContent" :navigation="element.folderContent" :parentId="element.keyPath" :expanded="expanded" />
</li>
</template>
<template #footer>

View File

@@ -0,0 +1,146 @@
const posts = Vue.createApp({
template: `<section id="posts" v-if="showPosts" class="px-12 py-8 bg-stone-50 shadow-md mb-16">
<div class="w-full relative">
<label class="block mb-1 font-medium">{{ $filters.translate('Short title for post') }}</label>
<div class="flex">
<input
class="h-12 w-3/4 border px-2 py-3 border-stone-300 bg-stone-200"
v-model="posttitle"
type="text"
maxlength="60"
name="title"
placeholder="maximum 60 characters"
/>
<button
class="w-1/4 px-2 py-3 ml-2 text-stone-50 bg-stone-700 hover:bg-stone-900 hover:text-white transition duration-100 cursor-pointer disabled:cursor-not-allowed disabled:bg-stone-200 disabled:text-stone-800"
type="button"
@click.prevent="createPost()"
>
{{ $filters.translate('create post') }}
</button>
</div>
<div v-if="error" class="f6 tm-red mt1">{{ error }}</div>
</div>
<div>
<single-post
v-for="post in posts"
:key="post.keyPath"
:post="post"
:editormode="editormode"
:baseurl="baseurl"
></single-post>
</div>
</section>`,
data: function () {
return {
active: true,
item: data.item,
posts: false,
posttitle: '',
format: /[@#*()=\[\]{};:"\\|,.<>\/]/,
baseurl: data.urlinfo.baseurl,
editormode: data.settings.editor,
error: false
}
},
mounted() {
eventBus.$on('showEditor', this.showPostlist );
eventBus.$on('hideEditor', this.hidePostlist );
eventBus.$on('item', item => {
this.item = item;
});
if(this.item.elementType == "folder" && this.item.contains == "posts")
{
this.posts = this.item.folderContent;
}
},
computed: {
showPosts()
{
if(this.item.elementType == "folder" && this.item.contains == "posts" && this.active)
{
return true;
}
return false;
}
},
methods: {
showPostlist()
{
this.active = true;
},
hidePostlist()
{
this.active = false;
},
createPost(evt)
{
eventBus.$emit('publisherclear');
if(this.format.test(this.posttitle) || this.posttitle == '' || this.posttitle.length > 60)
{
eventBus.$emit('publishermessage', 'Special Characters are not allowed. Length between 1 and 60.');
return;
}
var self = this;
tmaxios.post('/api/v1/post',{
'folder_id': this.item.keyPath,
'item_name': this.posttitle,
'type': 'file',
})
.then(function (response)
{
if(response.data.item)
{
self.posts = response.data.item.folderContent;
self.posttitle = '';
}
})
.catch(function (error)
{
if(error.response)
{
eventBus.$emit('publishermessage', error.response.data.errors);
}
});
}
}
})
posts.component('single-post',{
props: ['post', 'baseurl', 'editormode'],
template: `<div class="my-4">
<a :href="getUrl(post.urlRelWoF)" :class="getBorderStyle(post.status)" class="border-l border-l-4 w-full inline-block p-4 bg-stone-100 hover:bg-stone-200 transition duration-100">
<h4 class="text-l font-bold">{{ post.name }} <span class="float-right text-xs font-normal">{{ getDate(post.order) }}</span></h4>
</a>
</div>`,
methods: {
getBorderStyle(status)
{
if(status == 'published')
{
return "border-teal-500";
}
if(status == 'modified')
{
return "border-yellow-400";
}
if(status == 'unpublished')
{
return "border-rose-500";
}
},
getUrl(posturl)
{
return this.baseurl + '/tm/content/' + this.editormode + this.post.urlRelWoF;
},
getDate(str)
{
var cleandate = [str.slice(0,4), str.slice(4,6), str.slice(6,8)];
return cleandate.join("-");
}
}
})

View File

@@ -66,6 +66,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
$group->post('/article', ControllerApiAuthorArticle::class . ':createArticle')->setName('api.article.create')->add(new ApiAuthorization($acl, 'content', 'create')); # author
$group->put('/draft', ControllerApiAuthorArticle::class . ':updateDraft')->setName('api.draft.update')->add(new ApiAuthorization($acl, 'content', 'create')); # author
$group->post('/draft/publish', ControllerApiAuthorArticle::class . ':publishDraft')->setName('api.draft.publish')->add(new ApiAuthorization($acl, 'content', 'create')); # author
$group->post('/post', ControllerApiAuthorArticle::class . ':createPost')->setName('api.post.create')->add(new ApiAuthorization($acl, 'content', 'create'));
# BLOCKS
$group->post('/block', ControllerApiAuthorBlock::class . ':addBlock')->setName('api.block.add')->add(new ApiAuthorization($acl, 'mycontent', 'create'));