feat(float): rebuild right floating buttons 1:1 with reference theme

- Storage: aksrt_floating_buttons now uses reference float_btn shape
  (fixed 8 sortable types, more-buttons group); single source of
  defaults/sanitize in AKSRT\Settings\FloatButtons
- Admin SPA: CSF-parity page split into components/floating/*
- Frontend: aksrt_float_right() on wp_footer mirrors zib_float_right()
  (wp_is_mobile, PC tooltip, toggle-theme, ontop/scrollTopTo, qrcode,
  wechat/qq, pay_vip, add, more)
- CSS: floating-buttons.css rewritten from reference rules; legacy float
  rules removed from main/part/shell/overlay-primitives
- Remove dead code: reorder route/API, get_floating_buttons_items,
  floatingSchema, floatActionsId, float-theme-toggle bindings,
  common/floating.css, aksrt-runtime-win.php, .aksrt-float-btn
This commit is contained in:
2026-09-26 20:44:50 +08:00
parent 7f60071460
commit 6f2adfaef1
32 changed files with 1338 additions and 3111 deletions
@@ -96,26 +96,33 @@ test('FA5 loads under a single canonical handle everywhere', () => {
test('custom settings pages and standalone save paths speak the three icon formats', () => {
// 手机底部Tab / 右侧悬浮按钮 custom pages render the shared picker.
for (const page of ['assets/admin/src/pages/content/FooterTabbarSettings.vue', 'assets/admin/src/pages/content/FloatingButtonsSettings.vue']) {
// FaIconPicker declares modelValue / emits update:modelValue; the Naive UI
// v-model:value convention binds a prop the picker never reads, so
// selection clicks would be dropped.
const pickerFiles = [
'assets/admin/src/pages/content/FooterTabbarSettings.vue',
'assets/admin/src/components/floating/FloatBtnItemPanel.vue',
'assets/admin/src/components/floating/FloatMoreButtonsEditor.vue',
];
for (const page of pickerFiles) {
const source = read(page);
assert.match(source, /import FaIconPicker from '@\/components\/form\/FaIconPicker\.vue'/);
assert.match(source, /import IconValue from '@\/components\/form\/IconValue\.vue'/);
assert.match(source, /<FaIconPicker/, `${page} uses the picker for icon fields`);
// FaIconPicker declares modelValue / emits update:modelValue; the Naive UI
// v-model:value convention binds a prop the picker never reads, so
// selection clicks would be dropped.
assert.doesNotMatch(source, /<FaIconPicker[^>]*v-model:value/, `${page} must not bind the picker with v-model:value`);
assert.match(source, /<FaIconPicker v-model=/, `${page} binds the picker with v-model`);
}
assert.match(read('assets/admin/src/pages/content/FooterTabbarSettings.vue'), /import IconValue from '@\/components\/form\/IconValue\.vue'/);
assert.match(read('assets/admin/src/pages/content/FooterTabbarSettings.vue'), /<FaIconPicker v-model=/);
assert.match(read('assets/admin/src/components/floating/FloatBtnItemPanel.vue'), /<FaIconPicker v-model=/);
assert.match(read('assets/admin/src/components/floating/FloatMoreButtonsEditor.vue'), /import IconValue from '@\/components\/form\/IconValue\.vue'/);
// Their REST save paths keep customsvg markup intact.
const controller = read('src/Admin/GlobalControllerPart1.php');
assert.match(controller, /aksrt_sanitize_icon_value\(/);
assert.doesNotMatch(controller, /preg_match\('\/\^fa\\s\+/, 'no FA-only regex gate on icon fields');
assert.match(read('src/Settings/FloatButtons.php'), /aksrt_sanitize_icon_value\(/);
// Frontend consumers render every format.
const footer = read('footer.php');
assert.match(footer, /aksrt_icon_markup\(\(string\) \$tab_icon\)/);
assert.match(footer, /aksrt_icon_markup\(\(string\) \$item_icon\)/);
assert.match(read('inc/helpers/aksrt-runtime-win.php'), /aksrt_icon_markup\(\$icon\)/);
assert.match(read('inc/helpers/aksrt-float-buttons.php'), /aksrt_icon_markup\(\(string\) \(\$opt\['icon'\]/);
// The popover tabs must wrap instead of overflowing the panel.
assert.match(read('assets/admin/src/components/form/FaIconPicker.vue'), /flex-wrap: wrap/);
});