<file_sep><?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class PostSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $posts = [];
        $now = now();

        $body = file_get_contents(__DIR__ . '/data/post.json');
        $summary = 'This is meaningless text meant to demonstrate an actual post summary for the blog layout.';

        for ($i = 1; $i <= 10; $i++) {
            $posts[] = [
                'category'      => "tech",
                'title'         => "Post $i",
                'slug'          => "post-$i",
                'summary'       => "This Post $i on Maseno Hub blog. $summary",
                'body'          => $body,
                'author_id'     => 1,
                'published_at'  => random_int(0, 10) < 7 ? $now : null,
                'created_at'    => $now,
                'updated_at'    => $now
            ];
        }

        DB::table('posts')->insert($posts);
    }
}
<file_sep><p align="center">
    <a href="#" target="_blank">
        <img src="public/logo.svg" width="256">
    </a>
</p>

<p align="center">
    <a href="https://github.com/sixpeteunder/website/actions/workflows/code-quality.yml" target="_blank">
        <img src="https://github.com/sixpeteunder/website/actions/workflows/code-quality.yml/badge.svg">
    </a>
</p>

## Website

Maseno Hub website concept. Built on [TALL](tallstack.dev/) stack:

- [TailwindCSS 2.x](https://tailwindcss.com/)
- [AlpineJS 2.x](https://github.com/alpinejs/alpine)
- [Laravel 8.x](https://laravel.com)
- [Livewire 2.x](https://laravel-livewire.com)

## Project setup
```bash
composer install
npm install
```

## Build frontend assets
```bash
npm run dev
```

## Migrate database
```bash
php artisan migrate
```

## Start a local development server
```bash
php artisan serve
```

## Contributing

- Always start a feature branch, avoid pushing directly to master.
- Make sure all checks are passing (use the `composer code:check` and `composer code:fix` helpers)

## License

The Maseno Hub website is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
<file_sep>import Quill from "quill";

const quill = new Quill("#body", { readOnly: true });
quill.setContents(JSON.parse(body));<file_sep><?php

namespace App\Http\Livewire\Admin;

use App\Models\User;
use Mediconesystems\LivewireDatatables\Http\Livewire\LivewireDatatable;

class UsersTable extends LivewireDatatable
{
    public $model = User::class;
    public $exportable = true;
    public $searchable = 'title';
    public $hideable = 'select';
    public $exclude = ['current_team_id'];
    public $sort = 'id|asc';

    // public function columns()
    // {
    //     //
    // }
}
<file_sep><?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use PhpParser\Node\Expr\PostDec;

/**
 * @property User $author
 * @property Post $newer
 * @property Post $older
 */
class Post extends Model
{
    use HasFactory;

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'published_at' => 'datetime',
    ];

    /**
     * Get the author that owns the post.
     */
    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }

    public function getNewerAttribute()
    {
        return self::where('id', '>', $this->id)->orderBy('id', 'asc')->first();
    }

    public function getOlderAttribute()
    {
        return self::where('id', '<', $this->id)->orderBy('id', 'desc')->first();
    }
}
<file_sep><?php

namespace App\Http\Controllers;

use App\Models\Question;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class QuestionController extends Controller
{
    public function index()
    {
        return view('questions.index', [
            'questions' => Question::with(['author', 'answers'])->get()
        ]);
    }

    public function new()
    {
        return view('questions.new');
    }

    public function create(Request $request)
    {
        $data = $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required', 'json',]
        ]);

        $question = new Question();
        $question->title = $data['title'];
        $question->body = $data['body'];
        $question->author()->associate(Auth::user());

        $question->save();
        return redirect()->route('questions.show', ['question' => $question->id]);
    }

    public function show($question)
    {
        return view('questions.show', [
            'question' => Question::with(['author', 'answers'])->find($question)
        ]);
    }
}
<file_sep><?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        return view('posts.index', [
            'posts' => Post::with(['author'])->whereNotNull(['published_at'])->orderBy('id', 'desc')->get()
        ]);
    }

    public function show($post)
    {
        return view('posts.show', [
            'post' => Post::with(['author'])->find($post)->append(['newer', 'older'])
        ]);
    }
}
<file_sep><?php

namespace App\Models;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Sluggable\{HasSlug, SlugOptions};

/**
 * @property Collection $answers
 * @property User $author
 */
class Question extends Model
{
    use HasFactory;
    use HasSlug;

    /**
     * Get the answers for the question.
     */
    public function answers()
    {
        return $this->hasMany(Answer::class);
    }

    /**
     * Get the author that owns the question.
     */
    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }

    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('title')
            ->saveSlugsTo('slug');
    }
}
<file_sep><?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class QuestionSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $questions = [];
        $now = now();

        $body = file_get_contents(__DIR__ . '/data/question.json');

        for ($i = 1; $i <= 10; $i++) {
            $questions[] = [
                'category'      => random_int(0, 10) > 5 ? 'programming' : 'tech support',
                'title'         => "Question $i",
                'slug'          => "question-$i",
                'body'          => $body,
                'author_id'     => 1,
                'created_at'    => $now,
                'updated_at'    => $now
            ];
        }

        DB::table('questions')->insert($questions);
    }
}
<file_sep>/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./node_modules/timeago.js/esm/format.js":
/*!***********************************************!*\
  !*** ./node_modules/timeago.js/esm/format.js ***!
  \***********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "format": () => (/* binding */ format)
/* harmony export */ });
/* harmony import */ var _utils_date__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/date */ "./node_modules/timeago.js/esm/utils/date.js");
/* harmony import */ var _register__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./register */ "./node_modules/timeago.js/esm/register.js");


/**
 * format a TDate into string
 * @param date
 * @param locale
 * @param opts
 */
var format = function (date, locale, opts) {
    // diff seconds
    var sec = (0,_utils_date__WEBPACK_IMPORTED_MODULE_0__.diffSec)(date, opts && opts.relativeDate);
    // format it with locale
    return (0,_utils_date__WEBPACK_IMPORTED_MODULE_0__.formatDiff)(sec, (0,_register__WEBPACK_IMPORTED_MODULE_1__.getLocale)(locale));
};
//# sourceMappingURL=format.js.map

/***/ }),

/***/ "./node_modules/timeago.js/esm/index.js":
/*!**********************************************!*\
  !*** ./node_modules/timeago.js/esm/index.js ***!
  \**********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "format": () => (/* reexport safe */ _format__WEBPACK_IMPORTED_MODULE_3__.format),
/* harmony export */   "render": () => (/* reexport safe */ _realtime__WEBPACK_IMPORTED_MODULE_4__.render),
/* harmony export */   "cancel": () => (/* reexport safe */ _realtime__WEBPACK_IMPORTED_MODULE_4__.cancel),
/* harmony export */   "register": () => (/* reexport safe */ _register__WEBPACK_IMPORTED_MODULE_2__.register)
/* harmony export */ });
/* harmony import */ var _lang_en_US__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lang/en_US */ "./node_modules/timeago.js/esm/lang/en_US.js");
/* harmony import */ var _lang_zh_CN__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./lang/zh_CN */ "./node_modules/timeago.js/esm/lang/zh_CN.js");
/* harmony import */ var _register__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./register */ "./node_modules/timeago.js/esm/register.js");
/* harmony import */ var _format__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./format */ "./node_modules/timeago.js/esm/format.js");
/* harmony import */ var _realtime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./realtime */ "./node_modules/timeago.js/esm/realtime.js");
/**
 * Created by hustcc on 18/5/20.
 * Contract: <EMAIL>
 */



(0,_register__WEBPACK_IMPORTED_MODULE_2__.register)('en_US', _lang_en_US__WEBPACK_IMPORTED_MODULE_0__.default);
(0,_register__WEBPACK_IMPORTED_MODULE_2__.register)('zh_CN', _lang_zh_CN__WEBPACK_IMPORTED_MODULE_1__.default);



//# sourceMappingURL=index.js.map

/***/ }),

/***/ "./node_modules/timeago.js/esm/lang/en_US.js":
/*!***************************************************!*\
  !*** ./node_modules/timeago.js/esm/lang/en_US.js ***!
  \***************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
var EN_US = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(diff, idx) {
    if (idx === 0)
        return ['just now', 'right now'];
    var unit = EN_US[Math.floor(idx / 2)];
    if (diff > 1)
        unit += 's';
    return [diff + " " + unit + " ago", "in " + diff + " " + unit];
}
//# sourceMappingURL=en_US.js.map

/***/ }),

/***/ "./node_modules/timeago.js/esm/lang/zh_CN.js":
/*!***************************************************!*\
  !*** ./node_modules/timeago.js/esm/lang/zh_CN.js ***!
  \***************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (/* export default binding */ __WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
var ZH_CN = ['秒', '分钟', '小时', '天', '周', '个月', '年'];
/* harmony default export */ function __WEBPACK_DEFAULT_EXPORT__(diff, idx) {
    if (idx === 0)
        return ['刚刚', '片刻后'];
    var unit = ZH_CN[~~(idx / 2)];
    return [diff + " " + unit + "\u524D", diff + " " + unit + "\u540E"];
}
//# sourceMappingURL=zh_CN.js.map

/***/ }),

/***/ "./node_modules/timeago.js/esm/realtime.js":
/*!*************************************************!*\
  !*** ./node_modules/timeago.js/esm/realtime.js ***!
  \*************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "cancel": () => (/* binding */ cancel),
/* harmony export */   "render": () => (/* binding */ render)
/* harmony export */ });
/* harmony import */ var _utils_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/dom */ "./node_modules/timeago.js/esm/utils/dom.js");
/* harmony import */ var _utils_date__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils/date */ "./node_modules/timeago.js/esm/utils/date.js");
/* harmony import */ var _register__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./register */ "./node_modules/timeago.js/esm/register.js");



// all realtime timer
var TIMER_POOL = {};
/**
 * clear a timer from pool
 * @param tid
 */
var clear = function (tid) {
    clearTimeout(tid);
    delete TIMER_POOL[tid];
};
// run with timer(setTimeout)
function run(node, date, localeFunc, opts) {
    // clear the node's exist timer
    clear((0,_utils_dom__WEBPACK_IMPORTED_MODULE_0__.getTimerId)(node));
    var relativeDate = opts.relativeDate, minInterval = opts.minInterval;
    // get diff seconds
    var diff = (0,_utils_date__WEBPACK_IMPORTED_MODULE_1__.diffSec)(date, relativeDate);
    // render
    node.innerText = (0,_utils_date__WEBPACK_IMPORTED_MODULE_1__.formatDiff)(diff, localeFunc);
    var tid = setTimeout(function () {
        run(node, date, localeFunc, opts);
    }, Math.min(Math.max((0,_utils_date__WEBPACK_IMPORTED_MODULE_1__.nextInterval)(diff), minInterval || 1) * 1000, 0x7fffffff));
    // there is no need to save node in object. Just save the key
    TIMER_POOL[tid] = 0;
    (0,_utils_dom__WEBPACK_IMPORTED_MODULE_0__.setTimerId)(node, tid);
}
/**
 * cancel a timer or all timers
 * @param node - node hosting the time string
 */
function cancel(node) {
    // cancel one
    if (node)
        clear((0,_utils_dom__WEBPACK_IMPORTED_MODULE_0__.getTimerId)(node));
    // cancel all
    // @ts-ignore
    else
        Object.keys(TIMER_POOL).forEach(clear);
}
/**
 * render a dom realtime
 * @param nodes
 * @param locale
 * @param opts
 */
function render(nodes, locale, opts) {
    // by .length
    // @ts-ignore
    var nodeList = nodes.length ? nodes : [nodes];
    nodeList.forEach(function (node) {
        run(node, (0,_utils_dom__WEBPACK_IMPORTED_MODULE_0__.getDateAttribute)(node), (0,_register__WEBPACK_IMPORTED_MODULE_2__.getLocale)(locale), opts || {});
    });
    return nodeList;
}
//# sourceMappingURL=realtime.js.map

/***/ }),

/***/ "./node_modules/timeago.js/esm/register.js":
/*!*************************************************!*\
  !*** ./node_modules/timeago.js/esm/register.js ***!
  \*************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "register": () => (/* binding */ register),
/* harmony export */   "getLocale": () => (/* binding */ getLocale)
/* harmony export */ });
/**
 * Created by hustcc on 18/5/20.
 * Contract: <EMAIL>
 */
/**
 * All supported locales
 */
var Locales = {};
/**
 * register a locale
 * @param locale
 * @param func
 */
var register = function (locale, func) {
    Locales[locale] = func;
};
/**
 * get a locale, default is en_US
 * @param locale
 * @returns {*}
 */
var getLocale = function (locale) {
    return Locales[locale] || Locales['en_US'];
};
//# sourceMappingURL=register.js.map

/***/ }),

/***/ "./node_modules/timeago.js/esm/utils/date.js":
/*!***************************************************!*\
  !*** ./node_modules/timeago.js/esm/utils/date.js ***!
  \***************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "toDate": () => (/* binding */ toDate),
/* harmony export */   "formatDiff": () => (/* binding */ formatDiff),
/* harmony export */   "diffSec": () => (/* binding */ diffSec),
/* harmony export */   "nextInterval": () => (/* binding */ nextInterval)
/* harmony export */ });
/**
 * Created by hustcc on 18/5/20.
 * Contract: <EMAIL>
 */
var SEC_ARRAY = [
    60,
    60,
    24,
    7,
    365 / 7 / 12,
    12,
];
/**
 * format Date / string / timestamp to timestamp
 * @param input
 * @returns {*}
 */
function toDate(input) {
    if (input instanceof Date)
        return input;
    // @ts-ignore
    if (!isNaN(input) || /^\d+$/.test(input))
        return new Date(parseInt(input));
    input = (input || '')
        // @ts-ignore
        .trim()
        .replace(/\.\d+/, '') // remove milliseconds
        .replace(/-/, '/')
        .replace(/-/, '/')
        .replace(/(\d)T(\d)/, '$1 $2')
        .replace(/Z/, ' UTC') // 2017-2-5T3:57:52Z -> 2017-2-5 3:57:52UTC
        .replace(/([+-]\d\d):?(\d\d)/, ' $1$2'); // -04:00 -> -0400
    return new Date(input);
}
/**
 * format the diff second to *** time ago, with setting locale
 * @param diff
 * @param localeFunc
 * @returns
 */
function formatDiff(diff, localeFunc) {
    /**
     * if locale is not exist, use defaultLocale.
     * if defaultLocale is not exist, use build-in `en`.
     * be sure of no error when locale is not exist.
     *
     * If `time in`, then 1
     * If `time ago`, then 0
     */
    var agoIn = diff < 0 ? 1 : 0;
    /**
     * Get absolute value of number (|diff| is non-negative) value of x
     * |diff| = diff if diff is positive
     * |diff| = -diff if diff is negative
     * |0| = 0
     */
    diff = Math.abs(diff);
    /**
     * Time in seconds
     */
    var totalSec = diff;
    /**
     * Unit of time
     */
    var idx = 0;
    for (; diff >= SEC_ARRAY[idx] && idx < SEC_ARRAY.length; idx++) {
        diff /= SEC_ARRAY[idx];
    }
    /**
     * Math.floor() is alternative of ~~
     *
     * The differences and bugs:
     * Math.floor(3.7) -> 4 but ~~3.7 -> 3
     * Math.floor(1559125440000.6) -> 1559125440000 but ~~1559125440000.6 -> 52311552
     *
     * More information about the performance of algebraic:
     * https://www.youtube.com/watch?v=65-RbBwZQdU
     */
    diff = Math.floor(diff);
    idx *= 2;
    if (diff > (idx === 0 ? 9 : 1))
        idx += 1;
    return localeFunc(diff, idx, totalSec)[agoIn].replace('%s', diff.toString());
}
/**
 * calculate the diff second between date to be formatted an now date.
 * @param date
 * @param relativeDate
 * @returns {number}
 */
function diffSec(date, relativeDate) {
    var relDate = relativeDate ? toDate(relativeDate) : new Date();
    return (+relDate - +toDate(date)) / 1000;
}
/**
 * nextInterval: calculate the next interval time.
 * - diff: the diff sec between now and date to be formatted.
 *
 * What's the meaning?
 * diff = 61 then return 59
 * diff = 3601 (an hour + 1 second), then return 3599
 * make the interval with high performance.
 **/
function nextInterval(diff) {
    var rst = 1, i = 0, d = Math.abs(diff);
    for (; diff >= SEC_ARRAY[i] && i < SEC_ARRAY.length; i++) {
        diff /= SEC_ARRAY[i];
        rst *= SEC_ARRAY[i];
    }
    d = d % rst;
    d = d ? rst - d : rst;
    return Math.ceil(d);
}
//# sourceMappingURL=date.js.map

/***/ }),

/***/ "./node_modules/timeago.js/esm/utils/dom.js":
/*!**************************************************!*\
  !*** ./node_modules/timeago.js/esm/utils/dom.js ***!
  \**************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "getDateAttribute": () => (/* binding */ getDateAttribute),
/* harmony export */   "setTimerId": () => (/* binding */ setTimerId),
/* harmony export */   "getTimerId": () => (/* binding */ getTimerId)
/* harmony export */ });
var ATTR_TIMEAGO_TID = 'timeago-id';
/**
 * get the datetime attribute, `datetime` are supported.
 * @param node
 * @returns {*}
 */
function getDateAttribute(node) {
    return node.getAttribute('datetime');
}
/**
 * set the node attribute, native DOM
 * @param node
 * @param timerId
 * @returns {*}
 */
function setTimerId(node, timerId) {
    // @ts-ignore
    node.setAttribute(ATTR_TIMEAGO_TID, timerId);
}
/**
 * get the timer id
 * @param node
 */
function getTimerId(node) {
    return parseInt(node.getAttribute(ATTR_TIMEAGO_TID));
}
//# sourceMappingURL=dom.js.map

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		if(__webpack_module_cache__[moduleId]) {
/******/ 			return __webpack_module_cache__[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/make namespace object */
/******/ 	(() => {
/******/ 		// define __esModule on exports
/******/ 		__webpack_require__.r = (exports) => {
/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 			}
/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
/******/ 		};
/******/ 	})();
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!*************************************!*\
  !*** ./resources/js/posts/index.js ***!
  \*************************************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var timeago_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! timeago.js */ "./node_modules/timeago.js/esm/index.js");

var nodes = document.querySelectorAll(".timeago");
(0,timeago_js__WEBPACK_IMPORTED_MODULE_0__.render)(nodes, "en_US");
})();

/******/ })()
;<file_sep><?php

use App\Http\Controllers\{
    EventController,
    PostController,
    ProjectController,
    QuestionController
};
use App\Models\{
    Answer,
    Event,
    Post,
    Project,
    Question,
    User
};
use Illuminate\Support\Facades\{Auth, Route};

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', fn () => view('home', [
    'event' => Event::with(['organizer'])->where('start', '>', now())->latest()->first(),
    'stats' => [
        'events'    => Event::count(),
        'projects'  => Project::count(),
        'questions' => Question::count(),
        'posts'     => Post::whereNotNull(['published_at'])->count()
    ]
]))->name('home');

Route::middleware(['auth:sanctum', 'verified'])
    ->get('/dashboard', fn () => view('dashboard'))
    ->name('dashboard');

Route::prefix('events')->name('events.')->group(function () {
    Route::get('', [EventController::class, 'index'])
        ->name('index');

    Route::get('/{event}', [EventController::class, 'show'])
        ->name('show')
        ->whereNumber('event');
});

Route::prefix('projects')->name('projects.')->group(function () {
    Route::get('', [ProjectController::class, 'index'])->name('index');

    Route::get('/{project}', [ProjectController::class, 'show'])
        ->name('show')
        ->whereNumber('project');
});

Route::prefix('questions')->name('questions.')->group(function () {
    Route::get('', [QuestionController::class, 'index'])->name('index');

    Route::middleware(['auth:sanctum', 'verified'])
        ->get('/new', [QuestionController::class, 'new'])
        ->name('new');

    Route::middleware(['auth:sanctum', 'verified'])
        ->post('/new', [QuestionController::class, 'create']);

    Route::get('/{question}', [QuestionController::class, 'show'])
        ->name('show')
        ->whereNumber('project');
});

Route::prefix('posts')->name('posts.')->group(function () {
    Route::get('', [PostController::class, 'index'])->name('index');

    Route::get('/{post}', [PostController::class, 'show'])
        ->name('show')
        ->whereNumber('post');
});

Route::prefix('users')->name('users.')->group(function () {
    Route::get('/{id}', fn ($id) => view('users.show', [
        'user'      => User::with(['events', 'projects', 'questions', 'answers', 'posts'])
            ->find($id),
        'questions' => Question::all(),
        'answers'   => Answer::with(['question'])->get(),
        'posts'     => Post::whereNotNull(['published_at'])->orderBy('id', 'desc')->get()
    ]))
        ->name('show');
});

Route::middleware(['auth:sanctum', 'verified'])
    ->get('/me', fn () => redirect(route('users.show', ['id' => Auth::id()])))
    ->name('me');

Route::get('/about', fn () => view('about'))->name('about');

// Administrative routes
Route::middleware([
    'auth:sanctum',
    'verified',
    'password.confirm',
    'can:administrate'
])
    ->prefix('admin')
    ->name('admin.')
    ->group(function () {
        Route::get('', fn () => view('admin.index', [
            'users'     => User::count(),
            'events'    => Event::count(),
            'projects'  => Project::count(),
            'questions' => Question::count(),
            'answers'   => Answer::count(),
            'posts'     => Post::whereNotNull(['published_at'])->count()
        ]))->name('index');

        Route::prefix('users')->name('users.')->group(function () {
            Route::get('', fn () => view('admin.users.index'))->name('index');
        });

        Route::prefix('events')->name('events.')->group(function () {
            Route::get('', fn () => view('admin.events.index'))->name('index');
        });

        Route::prefix('projects')->name('projects.')->group(function () {
            Route::get('', fn () => view('admin.projects.index'))->name('index');
        });

        Route::prefix('questions')->name('questions.')->group(function () {
            Route::get('', fn () => view('admin.questions.index'))->name('index');
        });

        Route::prefix('posts')->name('posts.')->group(function () {
            Route::get('', fn () => view('admin.posts.index'))->name('index');
        });
    });
<file_sep><?php

namespace Database\Seeders;

use Carbon\CarbonImmutable;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class EventSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $events = [];
        $now = now();

        for ($i = 1; $i <= 10; $i++) {
            $start = CarbonImmutable::createFromDate(month: $i);

            $events[] = [
                'title'         => "Event $i",
                'description'   => "Event $i starts on $start",
                'venue'         => "The usual place",
                'start'         => $start,
                'end'           => $start->addHours(4),
                'organizer_id'  => 1,
                'created_at'    => $now,
                'updated_at'    => $now
            ];
        }

        DB::table('events')->insert($events);
    }
}
<file_sep><?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

/**
 * @property User $organizer
 */
class Event extends Model
{
    use HasFactory;

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'start' => 'datetime',
        'end'   => 'datetime',
    ];

    /**
     * Get the organizer that owns the event.
     */
    public function organizer()
    {
        return $this->belongsTo(User::class, 'organizer_id');
    }
}
<file_sep><?php

namespace App\Http\Livewire\Admin;

use App\Models\Question;
use Mediconesystems\LivewireDatatables\Http\Livewire\LivewireDatatable;

class QuestionsTable extends LivewireDatatable
{
    public $model = Question::class;
    public $exportable = true;
    public $searchable = 'title';
    public $hideable = 'select';
    public $exclude = ['body'];
    public $sort = 'id|asc';

    // public function columns()
    // {
    //     //
    // }
}
<file_sep><?php

namespace App\Http\Controllers;

use App\Models\Event;
use Illuminate\Http\Request;
use Spatie\CalendarLinks\Link;

class EventController extends Controller
{
    public function index()
    {
        return view('events.index', [
            'events' => Event::with(['organizer'])->get()
        ]);
    }

    public function show($eventId)
    {
        $event = Event::with(['organizer'])->find($eventId);

        return view('events.show', [
            'event' => $event,
            'link'  => Link::create($event->title, $event->start, $event->end)
                ->address($event->venue)
                ->description($event->description)
        ]);
    }
}
<file_sep>import { Calendar } from "@fullcalendar/core";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from "@fullcalendar/list";

const calendarElement = document.getElementById("calendar");

const calendar = new Calendar(calendarElement, {
    plugins: [dayGridPlugin, timeGridPlugin, listPlugin],
    initialView: "dayGridMonth",
    headerToolbar: {
        left: "prev,next today",
        center: "title",
        right: "dayGridMonth,listWeek,timeGridDay",
    },

    events: events,

    weekNumbers: true,
    navLinks: true,

    // businessHours: {
    //     daysOfWeek: [1, 2, 3, 4, 5],
    //     startTime: "07:00",
    //     endTime: "16:00",
    // },

    dayMaxEventRows: true,
    // eventClick: function (info) {
    //     const event = info.event;
    // },
});

calendar.render();<file_sep>import Quill from "quill";

const quill = new Quill("#body", {
    placeholder: 'Give additional information that would enable respondents to understand your question.',
    theme: "snow"
});

const form = document.getElementById("question");
const body = document.querySelector("input[name=body]");

form.onsubmit = function() {
    body.value = JSON.stringify(quill.getContents());
    return true;
};
<file_sep><?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class AnswerSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $answers = [];
        $now = now();

        for ($i = 0; $i < 100; $i++) {
            $question = floor($i / 10);
            $questionAnswer = ($i - ($question * 10)) + 1;
            $question++;

            $answers[] = [
                'text'          => "Answer #$questionAnswer to question #$question.",
                'author_id'     => 1,
                'question_id'   => $question,
                'created_at'    => $now,
                'updated_at'    => $now
            ];
        }

        DB::table('answers')->insert($answers);
    }
}
<file_sep><?php

namespace App\Providers;

use App\Models\User;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * List of all administrative user types
     */
    private const ADMINISTRATORS = ['admin', 'editor'];

    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // This Gate checks whether the user has any business in the admin dashboard
        Gate::define('administrate', function (User $user) {
            return in_array($user->role, self::ADMINISTRATORS)
                || $user->events->count() > 0
                || $user->posts->count() > 0;
        });
    }
}
<file_sep><?php

namespace App\Http\Livewire\Admin;

use App\Models\Project;
use Mediconesystems\LivewireDatatables\Http\Livewire\LivewireDatatable;

class ProjectsTable extends LivewireDatatable
{
    public $model = Project::class;
    public $exportable = true;
    public $searchable = 'title';
    public $hideable = 'select';
    public $sort = 'id|asc';

    // public function columns()
    // {
    //     //
    // }
}
<file_sep><?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class ProjectSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $projects = [];
        $now = now();

        for ($i = 1; $i <= 10; $i++) {
            $projects[] = [
                'title'         => "Project $i",
                'url'           => "https://github.com/masenohub",
                'description'   => "Project $i by Maseno Hub organization.",
                'lead_id'       => 1,
                'created_at'    => $now,
                'updated_at'    => $now
            ];
        }

        DB::table('projects')->insert($projects);
    }
}
<file_sep><?php

namespace App\Http\Controllers;

use App\Models\Project;
use Illuminate\Http\Request;
use OpenGraph;
use shweshi\OpenGraph\Exceptions\FetchException;

class ProjectController extends Controller
{
    public function index()
    {
        return view('projects.index', [
            'projects' => Project::with(['lead'])->get()
        ]);
    }

    public function show($project)
    {
        $project = Project::with(['lead'])->find($project);

        try {
            // TODO: Implement OpenGraph stuff in frontend
            // @phpstan-ignore-next-line
            $project->open_graph = OpenGraph::fetch($project->url);
        } catch (FetchException) {
        }

        return view('projects.show', [
            'project' => $project
        ]);
    }
}
