OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
@props([
'contentRole' => '', // ''(none) | default | primary | secondary | tertiary(=ghost) | accent | link
'state' => '', // info | warning | danger | success
'scale' => '', // xs | s | m | l | xl
'variant' => '', // 'outline' = outline style (btn-outline / btn-{state}-outline)
'tag' => 'button', // a | button | input (the polymorphic element)
'link' => null, // href, when tag="a"; null => emit no href (e.g. <a onclick> with no href)
'inputType' => null, // submit | button | reset, when tag="button"/"input" (default below)
'leadingVisual' => '', // icon class, e.g. "fa fa-plus"
'trailingVisual' => '', // icon class
'labelText' => '', // text label; falls back to the slot
])
{{--
forms.button NO-OP button.
Renders the exact Bootstrap/forms.css classes the app uses TODAY (btn, btn-primary,
btn-danger, btn-small, ) so there is zero visual change. Call-sites are written against
the canonical prop vocabulary (contentRole/state/scale); at design time ONLY the maps
below + the CSS change, restyling every button from one place. See COMPONENTS.md.
Migration cheatsheet (today's class -> prop):
btn-primary -> contentRole="primary" btn-default -> contentRole="default"
btn-secondary -> contentRole="secondary" btn-transparent -> contentRole="ghost"
btn-link -> contentRole="link" btn-danger/info/success/warning -> state="…"
btn-small/btn-large -> scale="s"/"l" extra classes -> pass as class="…"
JS-coupled buttons (.dropdown-toggle) are migrated in the dropdown phase, not here.
--}}
@php
// Role carries the emphasis AND its default look:
// primary = filled, secondary = outline, tertiary/ghost = transparent (low-chrome).
// (variant="outline" is only needed to force outline on a non-secondary role, e.g. state+outline.)
$roleClass = match ($contentRole) {
'primary' => 'btn-primary',
'secondary' => 'btn-outline',
'default' => 'btn-default',
'tertiary', 'ghost' => 'btn-transparent',
'accent' => 'btn-primary',
'link' => 'btn-link',
default => '',
};
// State color is mutually exclusive with the role color today (a danger button is
// `btn btn-danger`, not `btn-primary btn-danger`). If a state is given, it wins.
$stateClass = match ($state) {
'danger' => 'btn-danger',
'warning' => 'btn-warning',
'success' => 'btn-success',
'info' => 'btn-info',
default => '',
};
$scaleClass = match ($scale) {
'xs', 's', 'sm' => 'btn-small',
'l', 'lg', 'xl' => 'btn-large',
default => '',
};
// variant="outline" selects the outline button style — btn-outline, or btn-{state}-outline
// (e.g. btn-danger-outline). This is the same style the edit-ticket save / "Save & Close"
// buttons use. Outline overrides the role color.
if ($variant === 'outline') {
// Only build btn-{state}-outline for a VALIDATED state (so state="default" -> btn-outline,
// never btn-default-outline). $stateClass is '' for default/unknown states.
$colorClass = $stateClass !== '' ? 'btn-'.$state.'-outline' : 'btn-outline';
} else {
$colorClass = $stateClass !== '' ? $stateClass : $roleClass;
}
$classes = trim('btn '.$colorClass.' '.$scaleClass);
// Inner content: leading icon + (labelText or slot) + trailing icon, matching the
// hand-written "<i class="fa …"></i> Label" markup buttons use today.
$hasLabel = trim($labelText) !== '';
@endphp
@if ($tag === 'input')
<input
type="{{ $inputType ?? 'submit' }}"
value="{{ $hasLabel ? $labelText : trim($slot) }}"
{{ $attributes->merge(['class' => $classes]) }}
/>
@elseif ($tag === 'a')
{{-- emit href only when a link is given, so <a onclick> without href stays href-less --}}
<a {{ $attributes->merge(['class' => $classes] + ($link !== null ? ['href' => $link] : [])) }}>
@if ($leadingVisual)<i class="{{ $leadingVisual }}"></i> @endif{{ $hasLabel ? $labelText : $slot }}@if ($trailingVisual) <i class="{{ $trailingVisual }}"></i>@endif
</a>
@else
{{-- Bare <button> emits NO type so the native default (submit inside a form) is preserved;
pass inputType only when the source had an explicit type. --}}
<button @if ($inputType !== null) type="{{ $inputType }}" @endif {{ $attributes->merge(['class' => $classes]) }}>
@if ($leadingVisual)<i class="{{ $leadingVisual }}"></i> @endif{{ $hasLabel ? $labelText : $slot }}@if ($trailingVisual) <i class="{{ $trailingVisual }}"></i>@endif
</button>
@endif

View File

@@ -0,0 +1,70 @@
@props([
// NO-OP variant -> the class the app renders TODAY. Default '' = a bare, unclassed input
// (the common case: ~206 inputs have no class and are styled by their form/context).
'variant' => '', // '' (bare) | headline | large | small
// Only EVIDENCE-BACKED, visually-distinct variants exist here:
// headline -> .main-title-input (large 24/26px title font, drop-shadow removed)
// large -> .input-large (fixed 210px width — width only)
// small -> .input-small (fixed 90px width — width only)
// NO "form" or "legacy" variant: `.form-control` and `.input` are pure Bootstrap
// cruft — forms.css element selectors override them, so a bare input is identical.
// (Ghost/inline-edit `.secretInput` is a real future variant, pending its async-save JS.)
'type' => 'text', // text | email | password | number | url | tel | search (HTML-native; Blade extracts it from $attributes so it never duplicates)
// --- design-system IDL: declared for the durable contract, but intentionally NOT rendered
// in no-op mode (a label/validation wrapper would change today's markup). They become
// active when the design phase introduces the field-row/label layout. ---
'contentRole' => '', // reserved
'state' => '', // info | warning | danger | success (validation) — reserved
'scale' => '', // xs | s | m | l | xl — reserved
'labelPosition' => 'top', // reserved
'labelText' => '', // reserved
'caption' => '', // reserved
'validationText' => '', // reserved
'validationState' => '', // reserved
'leadingVisual' => '', // reserved
'trailingVisual' => '', // reserved
])
{{--
forms.text-input NO-OP text input.
Renders a plain <input> with the SAME class the app uses TODAY (default: NO class). Every
other attribute (name, id, value, placeholder, style, data-*, hx-*, autocomplete, required,
maxlength, autofocus, ) passes straight through via $attributes. Zero visual/behaviour change.
⚠️ DO NOT route JS-coupled inputs through this component they will break. Keep these RAW:
date pickers: .dates .duedates .quickDueDates .dateFrom .dateTo .editFrom .editTo
.startDate .endDate .projectDateFrom .projectDateTo .week-picker .hasDatepicker
#deadline #sprintStart #sprintEnd #event_date_* #date #startDate #endDate #timesheetdate …
time: .timepicker, type="time" tags: #tags .tagsinputField data-role="tagsinput"
inline-edit: .secretInput .asyncInputUpdate (+ data-label / data-id)
color: .simpleColorPicker any inline onchange/onblur/onkeyup/oninput handler
See COMPONENTS.md for the full do-not-touch list.
Pass the input type via the HTML-native `type="…"` attribute. It is a declared @prop, so Blade
extracts it from the attribute bag the component emits exactly one `type` (never duplicated).
Omit it for a plain text input (default "text").
Migration cheatsheet (source class -> variant):
<input type="text" name=> -> <x-global::forms.text-input name=> (bare, no class)
<input type="email" class="form-control"> -> type="email" (drop form-control; it's redundant)
<input class="main-title-input"> -> variant="headline"
<input class="input-large"> -> variant="large"
<input class="input"> (no CSS / cruft) -> (bare; .input has no backing rule)
--}}
@php
// No-op map: variant -> the exact class the markup uses today.
$variantClass = match ($variant) {
'headline' => 'main-title-input',
'large' => 'input-large',
'small' => 'input-small',
default => '', // bare / search: no class (styled by context / id / name)
};
// Only add a class attribute when there's actually a class so a bare input stays
// class-less (no empty class="") exactly like today.
$attrs = $variantClass !== '' ? $attributes->merge(['class' => $variantClass]) : $attributes;
@endphp
<input type="{{ $type }}" {{ $attrs }} />

View File

@@ -0,0 +1,45 @@
@props([
// NO-OP textarea: renders a plain <textarea> with today's attributes + inner content.
// There is NO `variant` arm: the only textarea style-classes in the app (.tiptapSimple /
// .tiptapComplex / .wiki-editor-textarea) are JS rich-text EDITOR mounts — never route those
// through this component (see do-not-touch below). Plain textareas carry no distinct style
// class, so attribute + content passthrough is the whole no-op surface.
// --- design-system IDL: declared for the durable contract (shared with forms.text-input),
// intentionally NOT rendered in no-op mode (a label/validation wrapper would change
// today's markup). Activated in the design phase's field-row layout. ---
'contentRole' => '', // reserved
'state' => '', // info | warning | danger | success (validation) — reserved
'scale' => '', // xs | s | m | l | xl — reserved
'labelPosition' => 'top', // reserved
'labelText' => '', // reserved
'caption' => '', // reserved
'validationText' => '', // reserved
'validationState' => '', // reserved
])
{{--
forms.textarea NO-OP textarea.
Renders a plain <textarea> with the SAME attributes the app uses today. Every attribute EXCEPT the
declared @props above (name, id, rows, cols, placeholder, style, class, data-*, hx-*, required, )
passes through via $attributes Blade extracts declared props (state, contentRole, scale, labelText, )
so they are NOT emitted as HTML (they're reserved for the design phase). The field's value is the slot
(inner content), preserved EXACTLY.
⚠️ DO NOT route rich-text EDITOR textareas through this component JS upgrades them to Tiptap
and they will break. Keep these RAW:
Tiptap: class="tiptapSimple" / class="tiptapComplex" (JS scans `textarea.tiptapSimple` /
`textarea.tiptapComplex` and mounts an editor public/assets/js/app/core/tiptap/index.js)
Wiki editor: class="wiki-editor-textarea" (id="wikiArticleContent")
any textarea with an inline on* handler or a data-*editor* attribute.
⚠️ Whitespace matters: a textarea's value IS its inner content. Keep the slot tight —
<x-global::forms.textarea …>{{ $value }}</x-global::forms.textarea> — never add newlines/indent
around the value, or you change the field's content.
Migration:
<textarea name="x"></textarea> -> <x-global::forms.textarea name="x"></x-global::forms.textarea>
<textarea name="x">{{ $v }}</textarea> -> <x-global::forms.textarea name="x">{{ $v }}</x-global::forms.textarea>
--}}
<textarea {{ $attributes }}>{{ $slot }}</textarea>