OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
35
app/Domain/Plugins/Templates/marketplace.blade.php
Normal file
35
app/Domain/Plugins/Templates/marketplace.blade.php
Normal file
@@ -0,0 +1,35 @@
|
||||
@extends($layout)
|
||||
|
||||
@section('content')
|
||||
|
||||
<x-global::pageheader :icon="'fa fa-puzzle-piece'">
|
||||
<h1>App Marketplace</h1>
|
||||
</x-global::pageheader>
|
||||
|
||||
@displayNotification()
|
||||
|
||||
<div class="maincontent">
|
||||
|
||||
@include('plugins::partials.plugintabs', ["currentUrl" => "marketplace"])
|
||||
|
||||
<div class="maincontentinner">
|
||||
|
||||
<div class="tw-w-full"
|
||||
hx-get="{{ BASE_URL }}/hx/plugins/marketplaceplugins/getlist"
|
||||
hx-trigger="load"
|
||||
hx-target="#pluginList"
|
||||
hx-indicator=".htmx-indicator, .htmx-loaded-content"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div id="pluginList">
|
||||
<div class="htmx-indicator tw-ml-m tw-mr-m tw-pt-l">
|
||||
<x-global::loadingText type="plugincard" count="5" includeHeadline="false"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
75
app/Domain/Plugins/Templates/myapps.blade.php
Normal file
75
app/Domain/Plugins/Templates/myapps.blade.php
Normal file
@@ -0,0 +1,75 @@
|
||||
@extends($layout)
|
||||
|
||||
@section('content')
|
||||
|
||||
<x-global::pageheader :icon="'fa fa-puzzle-piece'">
|
||||
<h1>My Apps</h1>
|
||||
</x-global::pageheader>
|
||||
|
||||
@displayNotification()
|
||||
|
||||
<div class="maincontent">
|
||||
|
||||
@include('plugins::partials.plugintabs', ["currentUrl" => "installed"])
|
||||
|
||||
<div class="maincontentinner">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h5 class="subtitle" style="margin-bottom:15px;">
|
||||
{{ __("text.installed_plugins") }}
|
||||
</h5>
|
||||
<div class="row sortableTicketList">
|
||||
@each('plugins::partials.plugin', $installedPlugins, 'plugin')
|
||||
|
||||
@if ($installedPlugins === false || count($installedPlugins) == 0)
|
||||
<span class="tw-block tw-px-4 tw-mb-4">{{ __("text.no_plugins_activated") }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h5 class="subtitle tw-mb-m" style="margin-bottom:15px;">
|
||||
{{ __("text.new_plugins") }}
|
||||
</h5>
|
||||
<ul class="sortableTicketList" >
|
||||
@if (count($newPlugins) > 0)
|
||||
@foreach ($newPlugins as $newplugin)
|
||||
<li>
|
||||
<div class="ticketBox fixed">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-4">
|
||||
<strong>{{ $newplugin->name }}<br /></strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ $newplugin->description }}<br />
|
||||
{{ $tpl->__("text.version") }} {{ $newplugin->version }}
|
||||
@if (is_array($newplugin->authors) && count($newplugin->authors) > 0)
|
||||
| {{ $tpl->__("text.by") }} <a href="mailto:{{ $newplugin->authors[0]["email"] }}">{{ $newplugin->authors[0]["name"] }}</a>
|
||||
@endif
|
||||
| <a href="{{ $newplugin->homepage }}"> {{ $tpl->__("text.visit_site") }} </a>
|
||||
</div>
|
||||
<div class="col-md-4" style="padding-top:5px;">
|
||||
<x-global::forms.button tag="a" link="{{ BASE_URL }}/plugins/myapps?install={{ $newplugin->foldername }}" contentRole="default" class="pull-right">{{ $tpl->__('buttons.activate') }}</x-global::forms.button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
@else
|
||||
<x-global::undrawSvg image="undraw_empty_cart_co35.svg" headline="Nothing New">
|
||||
We couldn't discover any new plugins in your plugin folder, please make sure the plugin is unzipped and contains a composer.json file.
|
||||
</x-global::undrawSvg>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@if($plugin->type !== "system")
|
||||
<div style="padding-top:10px; padding-left:0px;">
|
||||
@if (!$plugin->enabled)
|
||||
<a href="{{ BASE_URL }}/plugins/myapps?enable={{ $plugin->id }}" class=""><i class="fa-solid fa-plug-circle-check"></i> {{ __('buttons.enable') }}</a> |
|
||||
<a href="{{ BASE_URL }}/plugins/myapps?remove={{ $plugin->id }}" class="delete"><i class="fa fa-trash"></i> {{ __('buttons.remove') }}</a>
|
||||
@else
|
||||
<a href="{{ BASE_URL }}/plugins/myapps?disable={{ $plugin->id }}" class="delete"><i class="fa-solid fa-plug-circle-xmark"></i> {{ __('buttons.disable') }}</a>
|
||||
@endif
|
||||
|
||||
@if ($plugin->enabled && file_exists(APP_ROOT . '/app/Plugins/' . $plugin->foldername . '/Controllers/Settings.php'))
|
||||
<a href="{{ BASE_URL }}/{{ $plugin->foldername }}/settings"><i class="fa fa-cog"></i> Settings</a>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<p>System Plugin, cannot be disabled or removed</p>
|
||||
@endif
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
<h1>Latest Plugin Updates</h1>
|
||||
<p>Extend Leantime using our latest plugins.<br /><a href="{{ BASE_URL }}/plugins/marketplace"><i class="fa fa-cogs"></i> Manage Your Apps</a></p><br />
|
||||
|
||||
<br />
|
||||
<div>
|
||||
<ul>
|
||||
|
||||
@foreach($plugins as $plugin)
|
||||
|
||||
<li onclick="window.location='#/plugins/details/{{ $plugin->identifier }}'">
|
||||
<img src="{{ $plugin->getPluginImageData() }}" width="75" height="75" class="tw-rounded tw-float-left tw-mr-m"/>
|
||||
@if (! empty($plugin->name))
|
||||
<a href="#/plugins/details/{{ $plugin->identifier }}"> <strong>{!! $plugin->name !!}</strong> {{ $plugin->version ? "(v".$plugin->version.")" : "" }}</a><br />
|
||||
<x-global::inlineLinks :links="$plugin->getMetadataLinks()" />
|
||||
@endif
|
||||
|
||||
@if (! empty($desc = $plugin->getCardDesc()))
|
||||
<p>{!! $desc !!}</p>
|
||||
@endif
|
||||
<div class="clearall"></div>
|
||||
<hr />
|
||||
|
||||
{{-- <div class="row tw-mb-base">--}}
|
||||
{{-- <div class="col tw-flex tw-flex-col tw-gap-base">--}}
|
||||
|
||||
|
||||
{{-- <div class="tw-flex tw-flex-row tw-gap-base">--}}
|
||||
{{-- <div class="plugin-price tw-flex-1 tw-content-center" >--}}
|
||||
{{-- <strong>{!! $plugin->getPrice() !!}</strong><br />--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="tw-border-t tw-border-[var(--main-border-color)] tw-px-base tw-text-right tw-flex-1 tw-justify-items-end">--}}
|
||||
{{-- @include($plugin->getControlsView(), ["plugin" => $plugin])--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
</li>
|
||||
|
||||
@endforeach
|
||||
</ul>
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="tw-flex tw-justify-between tw-items-center tw-gap-base">
|
||||
<x-global::button type="primary" link="#/plugins/details/{{ $plugin->identifier }}">
|
||||
{{ __('marketplace.details_link') }}
|
||||
</x-global::button>
|
||||
</div>
|
||||
47
app/Domain/Plugins/Templates/partials/plugin.blade.php
Normal file
47
app/Domain/Plugins/Templates/partials/plugin.blade.php
Normal file
@@ -0,0 +1,47 @@
|
||||
@props([
|
||||
'plugin'
|
||||
])
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="ticketBox fixed" style="padding-top:0px; overflow: hidden; margin-bottom: 25px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12 tw-overflow-hidden tw-mb-m">
|
||||
<img src="{{ $plugin->getPluginImageData() }}" width="75" height="75" class="tw-rounded tw-mt-base"/>
|
||||
|
||||
@if($plugin instanceof \Leantime\Domain\Plugins\Models\MarketplacePlugin)
|
||||
<div
|
||||
class="certififed label-default tw-absolute tw-top-[10px] tw-right-[10px] tw-text-primary tw-rounded-full tw-text-sm"
|
||||
data-tippy-content="{{ __('marketplace.certified_tooltip') }}"
|
||||
>
|
||||
<i class="fa fa-certificate"></i>
|
||||
Certified
|
||||
</div>
|
||||
@endif
|
||||
<div class="clearall"></div>
|
||||
<div style="margin-top:10px;">
|
||||
@if (! empty($plugin->name))
|
||||
<strong style="font-size:var(--font-size-l);">{!! $plugin->name !!}</strong> {{ $plugin->version ? "(v".$plugin->version.")" : "" }}<br />
|
||||
<x-global::inlineLinks :links="$plugin->getMetadataLinks()" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row tw-mb-base">
|
||||
<div class="col tw-flex tw-flex-col tw-gap-base">
|
||||
|
||||
@if (! empty($desc = $plugin->getCardDesc()))
|
||||
<p>{!! $desc !!}</p>
|
||||
@endif
|
||||
<div class="tw-flex tw-flex-row tw-gap-base">
|
||||
<div class="plugin-price tw-flex-1 tw-content-center" >
|
||||
<strong>{!! $plugin->getPrice() !!}</strong><br />
|
||||
</div>
|
||||
<div class="tw-border-t tw-border-[var(--main-border-color)] tw-px-base tw-text-right tw-flex-1 tw-justify-items-end">
|
||||
@include($plugin->getControlsView(), ["plugin" => $plugin])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
33
app/Domain/Plugins/Templates/partials/pluginlist.blade.php
Normal file
33
app/Domain/Plugins/Templates/partials/pluginlist.blade.php
Normal file
@@ -0,0 +1,33 @@
|
||||
@props([
|
||||
'plugins'
|
||||
])
|
||||
|
||||
<div class="tw-w-full row">
|
||||
<div class="col-lg-12">
|
||||
<div class="row sortableTicketList">
|
||||
@if (count($plugins) == 0)
|
||||
<div class="tw-w-full htmx-loaded-content">
|
||||
<x-global::undrawSvg image="undraw_empty_cart_co35.svg" headline="Out of Stock">
|
||||
Due to a global bit shortage our plugins are currently out of stock. We are working hard to get more stock in as soon as possible.
|
||||
</x-global::undrawSvg>
|
||||
</div>
|
||||
@else
|
||||
|
||||
@foreach($plugins as $key => $pluginCategory)
|
||||
@if($key !== 'plugins')
|
||||
<div class="col-md-12">
|
||||
|
||||
<h1 style="border-bottom:1px solid rgba(0, 0, 0, 0.3); margin-bottom:10px; padding-bottom:10px;"><strong>{!! $pluginCategory['name'] !!}</strong></h1>
|
||||
<p style="">{!! $pluginCategory['description'] !!}</p> <br />
|
||||
|
||||
</div>
|
||||
@each('plugins::partials.plugin', $pluginCategory['plugins'], 'plugin')
|
||||
<div class="col-md-12" style="margin-bottom:20px;">
|
||||
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
19
app/Domain/Plugins/Templates/partials/plugintabs.blade.php
Normal file
19
app/Domain/Plugins/Templates/partials/plugintabs.blade.php
Normal file
@@ -0,0 +1,19 @@
|
||||
{{-- Standard floating tab nav (design call 2026-08-03) — the old
|
||||
.maincontentinner.tabs band CSS is gone, so this partial rides the
|
||||
shared lt-tabs structure like the ticket board tabs. --}}
|
||||
<div class="lt-tabs lt-tabs--floating lt-tabs--links hideOnPrint">
|
||||
<nav class="lt-tabs-group" aria-label="Apps">
|
||||
<ul>
|
||||
<li class="{{ $currentUrl == 'marketplace' ? "active" : "" }}">
|
||||
<a href="<?=BASE_URL ?>/plugins/marketplace">
|
||||
<i class="fa-solid fa-store"></i> Explore Apps
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{ $currentUrl == 'installed' ? "active" : "" }}">
|
||||
<a href="<?=BASE_URL ?>/plugins/myapps">
|
||||
<i class="fa-solid fa-puzzle-piece"></i> My Apps
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
178
app/Domain/Plugins/Templates/plugindetails.blade.php
Normal file
178
app/Domain/Plugins/Templates/plugindetails.blade.php
Normal file
@@ -0,0 +1,178 @@
|
||||
@extends($layout)
|
||||
|
||||
@section('content')
|
||||
<div class="tw-max-h-[90vh] tw-w-[40vw] tw-flex tw-flex-col tw-gap-base">
|
||||
<div class="tw-flex tw-gap-base tw-items-center">
|
||||
<img src="{{ $plugin->icon }}" width="175" height="175" class="tw-rounded">
|
||||
<div class="tw-flex tw-flex-col tw-gap-base">
|
||||
<h2 class="tw-text-2xl tw-flex tw-flex-col tw-gap-base">
|
||||
<span>{!! $plugin->name !!}</span>
|
||||
@if (!empty($plugin->vendorDisplayName) && !empty($plugin->vendorId))
|
||||
<small>{{ __('text.by') }} <a href="/plugins/marketplace?vendor_id={{ $plugin->vendorId }}">{{ $plugin->vendorDisplayName }}</a></small>
|
||||
@endif
|
||||
</h2>
|
||||
<p>
|
||||
@if ((int) $plugin->reviewCount > 0)
|
||||
<strong>Reviews:</strong> {{ $plugin->reviewCount }}<br>
|
||||
@endif
|
||||
|
||||
@if ((int) $plugin->rating > 0)
|
||||
<strong>Rating:</strong> {{ $plugin->rating }}<br>
|
||||
@endif
|
||||
|
||||
@if (! empty($plugin->categories))
|
||||
<strong>Categories:</strong> @foreach ($plugin->categories as $category)
|
||||
<x-global::badge :asLink="false">{{ $category['name'] }}</x-global::badge>
|
||||
@endforeach<br>
|
||||
@endif
|
||||
|
||||
@if (! empty($plugin->tags))
|
||||
<strong>Tags:</strong> @foreach ($plugin->tags as $tag)
|
||||
<x-global::badge :asLink="true" :url="'/plugins/marketplace?tag=' . $tag['slug']">{{ $tag['name'] }}</x-global::badge>
|
||||
@endforeach<br>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php
|
||||
// First visible tab is the initially-selected one (each section is conditional).
|
||||
$pluginTabOrder = array_values(array_filter([
|
||||
! empty($plugin->description) ? 'overview' : null,
|
||||
$plugin->reviewCount > 0 ? 'reviews' : null,
|
||||
! empty($plugin->compatibility) ? 'compatibility' : null,
|
||||
]));
|
||||
$pluginFirstTab = $pluginTabOrder[0] ?? null;
|
||||
@endphp
|
||||
@if ($pluginFirstTab !== null)
|
||||
<x-global::navigation.tabs group="plugindetails" label="Plugin details sections">
|
||||
@if (! empty($plugin->description))
|
||||
<x-global::navigation.tabs.tab name="overview" :selected="$pluginFirstTab === 'overview'">Overview</x-global::navigation.tabs.tab>
|
||||
@endif
|
||||
|
||||
@if ($plugin->reviewCount > 0)
|
||||
<x-global::navigation.tabs.tab name="reviews" :count="$plugin->reviewCount" :selected="$pluginFirstTab === 'reviews'">Reviews</x-global::navigation.tabs.tab>
|
||||
@endif
|
||||
|
||||
@if (! empty($plugin->compatibility))
|
||||
<x-global::navigation.tabs.tab name="compatibility" :selected="$pluginFirstTab === 'compatibility'">Compatibility</x-global::navigation.tabs.tab>
|
||||
@endif
|
||||
</x-global::navigation.tabs>
|
||||
@endif
|
||||
|
||||
<div class="tw-overflow-y-scroll tw-max-h-[600px]">
|
||||
@if (! empty($plugin->description))
|
||||
<x-global::navigation.tabs.panel name="overview" group="plugindetails">
|
||||
<div class="tw-pr-xs mce-content-body">{!! $plugin->description !!}</div>
|
||||
</x-global::navigation.tabs.panel>
|
||||
@endif
|
||||
|
||||
@if ($plugin->reviewCount > 0)
|
||||
<x-global::navigation.tabs.panel name="reviews" group="plugindetails">
|
||||
<div class="tw-flex tw-flex-col tw-gap-base">
|
||||
@foreach($plugin->reviews as $review)
|
||||
@if (is_array($review) || is_object($review))
|
||||
<div class="tw-border-b tw-border-gray-200 tw-pb-sm tw-mb-sm">
|
||||
@if (! empty($review['author'] ?? null))
|
||||
<strong>{{ $review['author'] }}</strong>
|
||||
@endif
|
||||
@if (! empty($review['rating'] ?? null))
|
||||
<span class="tw-text-sm tw-text-gray-500">({{ $review['rating'] }}/5)</span>
|
||||
@endif
|
||||
<p>{{ $review['content'] ?? $review['review'] ?? $review['text'] ?? '' }}</p>
|
||||
</div>
|
||||
@else
|
||||
<p>{{ $review }}</p>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</x-global::navigation.tabs.panel>
|
||||
@endif
|
||||
|
||||
@if (! empty($plugin->compatibility))
|
||||
<x-global::navigation.tabs.panel name="compatibility" group="plugindetails">
|
||||
<table class="tw-w-full tw-text-left tw-pt-base">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Plugin Version:</th>
|
||||
<th>Compatible With Leantime Versions:</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($plugin->compatibility as $compatibility)
|
||||
<tr>
|
||||
<td>{{ $compatibility['version_number'] }}</td>
|
||||
<td>{{ $compatibility['supported_version_from'] }} - {{ $compatibility['supported_version_to'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</x-global::navigation.tabs.panel>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-justify-between tw-items-center">
|
||||
@if (! empty($plugin->marketplaceUrl))
|
||||
<x-global::button
|
||||
:link="$plugin->marketplaceUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Get a license</x-global::button>
|
||||
@else
|
||||
<span>This plugin currently isn't available for purchase.</span>
|
||||
@endif
|
||||
|
||||
@fragment('plugin-installation')
|
||||
@if (! empty($plugin->marketplaceId))
|
||||
@if (isset($formNotification) && ! empty($formNotification))
|
||||
<div class="tw-text-green-500">{!! $formNotification !!}</div>
|
||||
@else
|
||||
<div id="installForm{{ $plugin->marketplaceId }}">
|
||||
|
||||
|
||||
@if (! empty($formError))
|
||||
<div class="tw-text-red-500">{!! $formError !!}</div>
|
||||
@endif
|
||||
|
||||
@if($isBundle === false)
|
||||
<form
|
||||
class="tw-flex tw-gap-2 tw-items-center"
|
||||
hx-post="{{ BASE_URL }}/hx/plugins/details/install"
|
||||
hx-swap="outerHTML"
|
||||
hx-indicator=".htmx-indicator-small, .htmx-loaded-content"
|
||||
hx-target="#installForm{{ $plugin->marketplaceId }}"
|
||||
>
|
||||
@php
|
||||
if (isset($plugin->version)) {
|
||||
unset($plugin->version);
|
||||
}
|
||||
@endphp
|
||||
@foreach ((array) $plugin as $prop => $value)
|
||||
<input type="hidden" name="plugin[{{ $prop }}]" value="{{ is_array($value) || is_object($value) ? json_encode($value) : $value }}" />
|
||||
@endforeach
|
||||
<select class="!tw-mb-none !tw-p-[4px]" name="plugin[version]">
|
||||
@foreach ($plugin->compatibility as $compatibility)
|
||||
<option value="{{ $compatibility['version_number'] }}">{{ $compatibility['version_number'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<x-global::forms.text-input class="!tw-mb-none !tw-p-[4px]" name="plugin[license]" placeholder="License Key" />
|
||||
<x-global::button
|
||||
:tag="'button'"
|
||||
:type="'secondary'"
|
||||
>Install</x-global::button>
|
||||
<div class="htmx-indicator-small">
|
||||
<x-global::loader id="loadingthis" size="25px" />
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<span>This plugin currently isn't available for installation.</span>
|
||||
@endif
|
||||
@endfragment
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user