OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
Some checks failed
Security Audit / Composer Security Audit (push) Has been cancelled
Security Audit / NPM Security Audit (push) Has been cancelled
Security Audit / CodeQL Analysis (push) Has been cancelled

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>