分页: 28 / 34
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 28日 11:38
由 ejsoon
在wordpress中,如何寫php,拿到指定的媒體庫文件的地址?即使這個文件改名改路徑也能拿到?
代码: 全选
在 WordPress 中,要確保「即便檔案改名或路徑變動」依然能拿到正確的地址,唯一的準則就是:**永遠使用 Attachment ID(附件 ID)**。
WordPress 的媒體庫文件本質上是一種名為 `attachment` 的 **Post Type**。當你在後台修改圖片的標題或透過外掛重新命名檔案時,其 ID 是永遠不變的。
以下是實現這一點的幾種最佳實踐:
---
### 1. 最核心的方法:`wp_get_attachment_url()`
只要你有該文件的 ID,無論它的實體路徑怎麼變(例如從 `/2023/01/` 移到 `/2024/05/`),這個函式都能動態從資料庫中抓取當前最新的 URL。
```php
// 假設附件 ID 為 520
$attachment_id = 520;
$url = wp_get_attachment_url($attachment_id);
if ($url) {
echo $url;
}
```
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 28日 11:43
由 ejsoon
如何在wordpress的媒體庫中顯示文件大小?
代码: 全选
要在 WordPress 媒體庫中顯示文件大小,最簡單且不依賴外掛的方法是透過 PHP 代碼。這可以讓你直接在媒體庫的列表視圖中看到每個檔案佔用的空間(例如 1.2 MB)。
你可以將以下代碼添加到主題的 `functions.php` 檔案中:
---
### PHP 代碼實現
```php
/**
* 在媒體庫列表增加「大小」欄位標題
*/
add_filter('manage_media_columns', 'add_file_size_column');
function add_file_size_column($columns) {
$columns['file_size'] = '檔案大小';
return $columns;
}
/**
* 抓取並顯示每個媒體文件的實際大小
*/
add_action('manage_media_custom_column', 'display_file_size_column', 10, 2);
function display_file_size_column($column_name, $post_id) {
if ($column_name === 'file_size') {
// 獲取檔案路徑
$file_path = get_attached_file($post_id);
if (file_exists($file_path)) {
// 獲取位元組數並轉換為易讀格式 (KB, MB)
$size = filesize($file_path);
echo size_format($size);
} else {
echo '未知';
}
}
}
/**
* 讓「大小」欄位支援點擊排序
*/
add_filter('manage_upload_sortable_columns', 'make_file_size_column_sortable');
function make_file_size_column_sortable($columns) {
$columns['file_size'] = 'file_size';
return $columns;
}
```
---
### 代碼功能詳解:
1. **`manage_media_columns`**: 這會在媒體庫的表格標題列插入一個新的「檔案大小」欄位。
2. **`get_attached_file($post_id)`**: 這是關鍵。它會根據我們之前聊過的 **Attachment ID** 找到伺服器上的實體路徑。
3. **`size_format()`**: 這是 WordPress 內建的函數,會自動幫你把 $1048576$ bytes 轉換成 `1 MB` 這種人類看得懂的格式,非常方便。
4. **排序功能**: 最後一段代碼讓你可以點擊標題,按檔案大小進行升冪或降冪排列,方便找出佔用空間的大檔案。
---
### 如何查看效果?
1. 保存 `functions.php` 後,進入後台 **媒體 > 媒體庫**。
2. **重要:** 必須切換到 **「列表視圖 (List View)」**(點擊左上角的清單圖示),而不是預設的網格圖示。
3. 你就會看到多出一欄「檔案大小」。
### 補充:如果不想寫代碼?
如果你偏好使用外掛,可以搜尋 **"Filesize in Media Library"** 或 **"Admin Columns"**,這些外掛也提供類似的功能,並支援更多自定義欄位。
你目前是想清理網站空間,還是單純想在選取檔案時有個參考?
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 28日 21:27
由 ejsoon
寫一個wordpress的short_code。
代码: 全选
寫一個wordpress的short_code。
一,short_code的名稱是「ejcees」,用法是[ejcees mode=single/mini]ejcees_content[/ejcees]。
二,它的html內容是:
<div class="ejcees" mode="single/mini">ejcees_content</div>
三,當頁面上存在至少一個[ejcees]時,需要引入以下js,只需引入一次:
<script src="ejceesobj.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.ejcees');
containers.forEach(container => {
const mode = container.getAttribute('mode');
new Ejcees(container, mode);
});
});
</script>
其中「<script src="ejceesobj.js"></script>」要改為:
$attachment_id = 19088;
$url = wp_get_attachment_url($attachment_id);
wp_enqueue_script('ejcees-obj-js')
四,ejcees_content要do_short_code以使其它short_code能用。
所有的代碼和注釋都要使用英文。
gemini fast:
代码: 全选
To implement this functionality, you can add the following code to your theme's `functions.php` file or a custom plugin.
This implementation uses `wp_enqueue_script` to handle the JS file and `wp_add_inline_script` to inject the initialization logic. This ensures the scripts are only loaded once and only when the shortcode is actually present on the page.
```php
/**
* Register the [ejcees] shortcode
*/
function register_ejcees_shortcode($atts, $content = null) {
// 1. Define default attributes and extract them
$options = shortcode_atts(array(
'mode' => 'single', // Default mode
), $atts);
// 2. Enqueue the external JS file via Attachment ID
$attachment_id = 19088;
$url = wp_get_attachment_url($attachment_id);
if ($url) {
// Register and enqueue the script
wp_enqueue_script('ejcees-obj-js', $url, array(), null, true);
// 3. Add the initialization inline script (runs only once)
$inline_init_script = "
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.ejcees');
containers.forEach(container => {
const mode = container.getAttribute('mode');
if (typeof Ejcees !== 'undefined') {
new Ejcees(container, mode);
}
});
});
";
wp_add_inline_script('ejcees-obj-js', $inline_init_script);
}
// 4. Process nested shortcodes within content
$processed_content = do_shortcode($content);
// 5. Return the HTML output
return sprintf(
'<div class="ejcees" mode="%s">%s</div>',
esc_attr($options['mode']),
$processed_content
);
}
add_shortcode('ejcees', 'register_ejcees_shortcode');
```
### Key Technical Details:
* **Script Management:** Using `wp_enqueue_script` ensures that even if you use the shortcode multiple times on one page, the `ejceesobj.js` file is only requested by the browser once.
* **Inline Logic:** `wp_add_inline_script` attaches your custom DOMContentLoaded logic directly after the source file, maintaining the correct execution order.
* **Security:** `esc_attr()` is used on the `mode` attribute to prevent XSS (Cross-Site Scripting) if a user enters malicious data into the shortcode attribute.
* **Nested Content:** `do_shortcode($content)` allows you to place other shortcodes (like galleries or buttons) inside `[ejcees]...[/ejcees]`.
---
### Usage Example:
In your WordPress editor, you can now use it like this:
`[ejcees mode="mini"]This is my content or [another_shortcode][/ejcees]`
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 28日 21:55
由 ejsoon
增加兩個參數:
一,[ejcees width="360"]
在html中就是style="width: 360px"
二,[ejcees inline]或[ejcees block]
在html中就是style="display: inline-block"或style="display: block; margin: 12px auto;"
這些參數都可以組合,組合時寫進同一個style中。
當沒寫這些參數時,默認值是「width=432 block」。
代码: 全选
根據您的需求,我更新了代碼。這版代碼增加了對 `width`、`inline` 與 `block` 參數的處理,並將它們整合到同一個 `style` 屬性中。
```php
/**
* Updated [ejcees] shortcode with width and display layout parameters
*/
function register_ejcees_shortcode($atts, $content = null) {
// 1. Define default attributes
// 'width' defaults to 432, and we use a custom flag for display logic
$atts = shortcode_atts(array(
'mode' => 'single',
'width' => '432',
'inline' => false,
'block' => false,
), $atts);
// 2. Logic for Display Style
// Default is block if neither 'inline' nor 'block' is explicitly provided as an attribute
$display_style = "";
// Check if 'inline' or 'block' is present in the $atts array
// (Shortcode boolean attributes usually appear as keys with empty values if written without ="")
if (isset($atts['inline']) && $atts['inline'] !== false) {
$display_style = "display: inline-block;";
} else {
// Default behavior or explicit 'block'
$display_style = "display: block; margin: 12px auto;";
}
// 3. Construct Style Attribute
$width_val = esc_attr($atts['width']);
$style_attr = "style=\"width: {$width_val}px; {$display_style}\"";
// 4. Enqueue Scripts (using Attachment ID 19088)
$attachment_id = 19088;
$url = wp_get_attachment_url($attachment_id);
if ($url) {
wp_enqueue_script('ejcees-obj-js', $url, array(), null, true);
$inline_init_script = "
document.addEventListener('DOMContentLoaded', () => {
const containers = document.querySelectorAll('.ejcees');
containers.forEach(container => {
const mode = container.getAttribute('mode');
if (typeof Ejcees !== 'undefined') {
new Ejcees(container, mode);
}
});
});
";
wp_add_inline_script('ejcees-obj-js', $inline_init_script);
}
// 5. Process Content and Output HTML
$processed_content = do_shortcode($content);
return sprintf(
'<div class="ejcees" mode="%s" %s>%s</div>',
esc_attr($atts['mode']),
$style_attr,
$processed_content
);
}
add_shortcode('ejcees', 'register_ejcees_shortcode');
```
### 代碼邏輯說明:
1. **默認值處理**:在 `shortcode_atts` 中設置 `width` 默認為 `432`。如果使用者沒有輸入 `inline`,邏輯會自動進入 `else` 分支,套用 `display: block; margin: 12px auto;`。
2. **參數組合**:所有樣式都會被合併到一個 `$style_attr` 變數中,最終輸出為一個 `style="..."` 屬性。
3. **用法範例**:
* **默認(width=432 block)**: `[ejcees]內容[/ejcees]`
* **修改寬度**: `[ejcees width="360"]內容[/ejcees]`
* **設為行內**: `[ejcees inline="true" width="200"]內容[/ejcees]`
*(註:WordPress 的 shortcode 在處理純關鍵字參數如 `[ejcees inline]` 時,建議寫成 `inline="1"` 或 `inline="true"` 以確保屬性被正確解析)*。
4. **嵌套支援**:保留了 `do_shortcode` 確保內部內容可以執行其他外掛的 shortcode。
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 28日 22:43
由 ejsoon
改進:
mini自動播放時顯示分支,文字背景透明度應為0.3,剛走完的棋應顯示綠邊和白點。
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 07:55
由 ejsoon
下面是對mini模式的改進:
一,把mini-branch-container的html放到ejceesctrl的下方,它的css將改成display:block,text-align:left。去掉svg分支圖標。不需要mini-branch-dropdown,直接把branch-move-link(如果有)放到mini-branch-container中。branch-move-link的display:inline-block,加虛線邊框,如果是active則是實線邊框。當不存在分支時,mini-branch-container的display:none。
二,把mini-comment-container的html放到ini-branch-container的下方,它的css將改成display:block,去掉它的背景色的設置,它的字體顏色就是#eee。當不存在評論時,mini-comment-container的display:none。
三,去掉BG Opacity、Text Opacity,換成max-height,意為mini-comment-container的最大高度,默認120px,step=10,min=60,max=360,超過scroll。
四,當點擊mini-exp-btn彈出 mini-exp-txt-textarea時,mini-branch-container和mini-comment-container將隱藏。
五,當點擊.ejcees的其它地方時,mini-settings-panel如果顯示將隱藏。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 08:00
由 ejsoon
gemini pro早上好:
代码: 全选
Here are the specific sections of the code that need to be modified to implement your requested changes for the `mini` mode. All code and comments are in English.
### 1. Update `constructor()`
Replace the `miniBgOpacity` and `miniTextOpacity` properties with the new `miniCommentMaxH` property.
```javascript
// In constructor()
// Remove these:
// this.miniBgOpacity = 0.6;
// this.miniTextOpacity = 1;
// Add this:
this.miniCommentMaxH = 120;
this.miniFontSize = 14;
```
### 2. Update `injectCSS()`
Modify the CSS rules for the `mini` mode. Replace the opacity variables, update the branch and comment containers, and remove the unused dropdown styles.
```javascript
// In injectCSS() within the `if (this.mode === 'single' || this.mode === 'mini') { ... }` block
afterContainerCSS = `
/* Use Container Queries instead of Media Queries */
@container chess-container (max-width: 767px) {
.ejcees-inner { flex-direction: column; }
.ejceesctrl { flex: 1 0 272px; min-height: 272px; }
.ejceessvg { width: 100%; height: auto; max-height: calc(100cqw * 480 / 432); }
.ejceespb { width: 100%; height: auto; max-height: 100%; }
.ejceestool, .ejceesstep, .ejceesoutput { padding: 0 7px; }
.ejceesstep { gap: 7px; }
.ejceesrecord { width: 200px; font-size: 12px; }
.ejceesrcdstep .ejceesrcdstepcontent { margin-left: 24px; }
.branch-marker { margin-left: 1px; }
.exp-fmt-outer { display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; }
.exp-fmt-btn { padding: 5px; cursor: pointer; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}
@container chess-container (min-width: 768px) {
.ejcees-inner { flex-direction: row; }
.ejceesctrl { flex: 1 0 414px; min-width: 414px; }
.ejceessvg { height: 100%; width: auto; max-width: calc(100cqh * 432 / 480); }
.ejceespb { height: 100%; width: auto; max-width: 100%; }
.ejceestool, .ejceesstep, .ejceesoutput { padding: 0 12px; }
.ejceesstep { gap: 12px; }
.ejceesrecord { width: 268px; font-size: 16px; }
}
/* mini specific elements */
.ejcees-inner.mini-mode {
--mini-fz: ${this.miniFontSize}px;
--mini-comment-max-h: ${this.miniCommentMaxH}px;
}
.mini-output-settings {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%237c7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>');
background-repeat: no-repeat;
background-position: center bottom;
background-size: 16px 16px;
cursor: pointer;
align-items: start;
overflow: visible;
font-size: 14px;
}
.mini-settings-panel {
position: absolute;
top: 8px;
right: 8px;
background-color: rgba(0, 0, 0, 0.85); /* Hardcoded solid dark background */
color: #fff;
padding: 12px;
border-radius: 6px;
z-index: 100;
display: flex;
flex-direction: column;
gap: 10px;
font-size: var(--mini-fz);
min-width: 240px;
}
.mini-settings-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.mini-settings-label {
flex: 0 0 100px;
white-space: nowrap;
}
.mini-settings-value {
flex: 0 0 30px;
text-align: right;
font-variant-numeric: tabular-nums;
color: #aaa;
}
.mini-settings-panel input[type="range"] {
flex: 1;
width: 100%;
}
.mini-branch-container {
display: none;
text-align: left;
padding: 8px;
font-size: var(--mini-fz);
}
.mini-branch-container .branch-move-link {
display: inline-block;
color: #eee;
padding: 4px 8px;
margin: 2px 4px 2px 0;
cursor: pointer;
border: 1px dashed #ccc;
border-radius: 4px;
}
.mini-branch-container .branch-move-link:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.mini-branch-container .branch-move-link.active {
font-weight: bold;
border: 1px solid #fff;
background-color: rgba(255, 255, 255, 0.2);
}
.mini-comment {
display: none;
color: #eee;
padding: 8px;
font-size: var(--mini-fz);
box-sizing: border-box;
white-space: pre-wrap;
word-break: break-all;
max-height: var(--mini-comment-max-h);
overflow-y: auto;
}
/* Button styling matching the dark theme */
.mini-exp-txt {
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
font-size: var(--mini-fz);
transition: background-color 0.2s;
}
.mini-exp-txt:hover {
background-color: rgba(255, 255, 255, 0.3);
}
/* Full-screen overlay panel */
.mini-exp-txt-panel {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85);
z-index: 200;
display: flex;
flex-direction: column;
}
/* Textarea styling */
.mini-exp-txt-textarea:focus,
.mini-exp-txt-textarea {
width: 100%;
height: 100%;
background: transparent;
color: #eee;
border: none;
padding: 40px 15px 15px 15px;
box-sizing: border-box;
font-size: 14px;
font-family: monospace;
resize: none;
outline: none;
}
/* Close button positioning and styling */
.mini-exp-txt-close {
position: absolute;
top: 5px;
right: 5px;
width: 36px;
height: 36px;
background: none;
border: none;
cursor: pointer;
color: white;
display: flex;
align-items: center;
justify-content: center;
z-index: 210;
}
.mini-exp-txt-close:hover {
color: #ff4444;
}
.ejceessvg > object {
width: 100%;
height: auto;
pointer-events: none;
}`;
```
### 3. Update `renderDOM()`
Move the branch and comment containers out of `.ejceessvg` and place them after `.ejceesctrl`. Update the `.mini-settings-panel` inputs.
```javascript
// In renderDOM(), if (this.mode === 'mini') { ... } block
if (this.mode === 'mini') {
this.container.innerHTML = `
<div class="ejcees-inner mini-mode" tabindex="-1" style="--mini-fz: ${this.miniFontSize}px; --mini-comment-max-h: ${this.miniCommentMaxH}px;">
<div class="ejceessvg" style="position: relative;" tabindex="-1">
<div id="mini-settings-panel" class="mini-settings-panel" style="display: none;">
<div class="mini-settings-row">
<span class="mini-settings-label">Max Height:</span>
<span class="mini-settings-value" id="val-comment-h">${this.miniCommentMaxH}</span>
<input type="range" id="mini-comment-h-slider" min="60" max="360" step="10" value="${this.miniCommentMaxH}">
</div>
<div class="mini-settings-row">
<span class="mini-settings-label">Font Size (px):</span>
<span class="mini-settings-value" id="val-fz">${this.miniFontSize}</span>
<input type="range" id="mini-fz-slider" min="12" max="24" step="1" value="${this.miniFontSize}">
</div>
<div class="mini-settings-row">
<span class="mini-settings-label">Interval (s):</span>
<span class="mini-settings-value" id="val-interval">${this.moveInterval}</span>
<input type="range" id="mini-interval-slider" min="1" max="4" step="0.2" value="${this.moveInterval}">
</div>
<div class="mini-settings-row">
<span class="mini-settings-label">Speed (s):</span>
<span class="mini-settings-value" id="val-speed">${this.moveSpeed}</span>
<input type="range" id="mini-speed-slider" min="0.1" max="2" step="0.1" value="${this.moveSpeed}">
</div>
<div style="text-align: center;">
<button type="button" class="mini-exp-txt" id="mini-exp-btn">Export Text</button>
</div>
</div>
</div>
<div class="ejceesctrl" style="flex: 0 0 auto; min-height: unset; height: 48px;">
<div class="ejceesstep">
<div class="ejceesoutput mini-output-settings">0/0</div>
<div class="ejceesstepminus">-</div>
<input type="range" class="ejceesstepdrop" min="0" max="0" value="0" />
<div class="ejceesstepplus">+</div>
<div class="note-btn" id="btn-autoplay" title="Auto Play" style="width: 36px; height: 36px; cursor: pointer; display: flex; align-items: center; justify-content: center; background-color: transparent;">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: #ccc;"><path d="M8 5v14l11-7z"/></svg>
</div>
</div>
</div>
<div id="mini-branch-container" class="mini-branch-container"></div>
<div id="mini-comment-container" class="mini-comment"></div>
<input type="file" id="file-input" style="display: none" />
</div>`;
}
```
### 4. Update `bindEvents()`
Refactor the event bindings for sliders, outside clicks, and the export text panel.
```javascript
// In bindEvents(), if (this.mode === 'mini') { ... } block
if (this.mode === 'mini') {
// -- Bind Settings UI --
const outputEl = this.container.querySelector('.ejceesoutput');
const settingsPanel = this.container.querySelector('#mini-settings-panel');
const innerWrapper = this.container.querySelector('.ejcees-inner');
if (outputEl && settingsPanel) {
outputEl.addEventListener('click', (e) => {
e.stopPropagation(); // Stop propagation to allow outside click logic
settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'flex' : 'none';
});
}
// Stop clicks inside the settings panel from closing it
if (settingsPanel) {
settingsPanel.addEventListener('click', (e) => e.stopPropagation());
}
// Close settings panel when clicking outside
this.container.addEventListener('click', () => {
if (settingsPanel && settingsPanel.style.display !== 'none') {
settingsPanel.style.display = 'none';
}
});
const commentHSlider = this.container.querySelector('#mini-comment-h-slider');
const commentHVal = this.container.querySelector('#val-comment-h');
if (commentHSlider && commentHVal) {
commentHSlider.addEventListener('input', e => {
this.miniCommentMaxH = e.target.value;
commentHVal.textContent = this.miniCommentMaxH;
innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
});
}
const fzSlider = this.container.querySelector('#mini-fz-slider');
const fzVal = this.container.querySelector('#val-fz');
if (fzSlider && fzVal) {
fzSlider.addEventListener('input', e => {
this.miniFontSize = e.target.value;
fzVal.textContent = this.miniFontSize;
innerWrapper.style.setProperty('--mini-fz', this.miniFontSize + 'px');
});
}
const intervalSlider = this.container.querySelector('#mini-interval-slider');
const intervalVal = this.container.querySelector('#val-interval');
if (intervalSlider && intervalVal) {
intervalSlider.addEventListener('input', e => {
this.moveInterval = parseFloat(e.target.value);
intervalVal.textContent = this.moveInterval;
});
}
const speedSlider = this.container.querySelector('#mini-speed-slider');
const speedVal = this.container.querySelector('#val-speed');
if (speedSlider && speedVal) {
speedSlider.addEventListener('input', e => {
this.moveSpeed = parseFloat(e.target.value);
speedVal.textContent = this.moveSpeed;
});
}
const exportBtn = this.container.querySelector('#mini-exp-btn');
if (exportBtn) {
exportBtn.addEventListener('click', () => {
// Hide branch and comment containers
const branchContainer = this.container.querySelector('#mini-branch-container');
const commentContainer = this.container.querySelector('#mini-comment-container');
if (branchContainer) branchContainer.style.display = 'none';
if (commentContainer) commentContainer.style.display = 'none';
// 1. Create the panel element
const panel = document.createElement('div');
panel.className = 'mini-exp-txt-panel';
// 2. Create the Close Button (SVG icon)
const closeBtn = document.createElement('button');
closeBtn.className = 'mini-exp-txt-close';
closeBtn.innerHTML = `
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
`;
// 3. Create the Textarea
const textarea = document.createElement('textarea');
textarea.className = 'mini-exp-txt-textarea';
// Use textContent from the container as requested
textarea.value = this.initialTextContent;
// 4. Close logic: Remove the panel from DOM and restore UI
closeBtn.onclick = () => {
panel.remove();
this.renderRecordUI();
this.renderNoteUI();
};
// 5. Assemble and append
panel.appendChild(closeBtn);
panel.appendChild(textarea);
this.container.appendChild(panel);
// Hide settings panel
if (settingsPanel) settingsPanel.style.display = 'none';
// 6. Focus and Select text automatically
textarea.focus();
textarea.select();
});
}
```
### 5. Update `renderRecordUI()`
Rewrite the `this.mode === 'mini'` block to render inline `.branch-move-link` buttons and handle `.mini-exp-txt-panel` blocking state.
```javascript
// Inside renderRecordUI()
renderRecordUI() {
if (this.mode === 'mini') {
const totalDepth = this.getPathDepth(this.historyFEN, this.currentBranch);
this.stepSlider.max = Math.max(0, totalDepth - 1);
const branchContainer = this.container.querySelector('#mini-branch-container');
const parentNode = this.getNodeAtStep(this.currentStepIndex - 1);
branchContainer.innerHTML = ''; // Clear existing contents
// Check if export panel is active
const isExportPanelActive = this.container.querySelector('.mini-exp-txt-panel') !== null;
if (!isExportPanelActive && parentNode && parentNode.v && parentNode.v.length > 1) {
branchContainer.style.display = 'block';
let forkIndex = -1;
let tempNode = this.historyFEN;
for (let j = 0; j < this.currentStepIndex - 1; j++) {
if (tempNode.v && tempNode.v.length > 1) forkIndex++;
const choice = tempNode.v && tempNode.v.length > 1 ? this.currentBranch[forkIndex] || 0 : 0;
tempNode = tempNode.v[choice];
}
forkIndex++;
// Add inline branch links
parentNode.v.forEach((child, idx) => {
const moveLink = document.createElement('div');
moveLink.className = 'branch-move-link';
if (this.currentBranch[forkIndex] === idx) {
moveLink.classList.add('active');
}
moveLink.innerText = this.NotationConverter.toChinese(child.move);
moveLink.onclick = e => {
e.stopPropagation();
this.updateBranchPath(forkIndex, idx);
this.jumpToStep(this.currentStepIndex);
this.renderRecordUI();
this.saveStateToUndo();
this.updateToolHighlights();
};
branchContainer.appendChild(moveLink);
});
} else {
branchContainer.style.display = 'none';
}
this.updateToolHighlights();
return;
}
```
### 6. Update `renderNoteUI()`
Ensure comments do not display when the export text panel is open.
```javascript
// Inside renderNoteUI()
renderNoteUI() {
if (this.mode === 'mini') {
const node = this.getNodeAtStep(this.currentStepIndex);
const commentContainer = this.container.querySelector('#mini-comment-container');
const isExportPanelActive = this.container.querySelector('.mini-exp-txt-panel') !== null;
if (!isExportPanelActive && node && node.c && node.c.trim() !== '') {
commentContainer.textContent = node.c.trim();
commentContainer.style.display = 'block';
} else {
commentContainer.style.display = 'none';
}
const autoBtnSvg = this.container.querySelector('#btn-autoplay svg');
if (autoBtnSvg) {
autoBtnSvg.innerHTML = this.isAutoPlaying
? '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>'
: '<path d="M8 5v14l11-7z"/>';
autoBtnSvg.style.fill = this.isAutoPlaying ? '#fff' : '#ccc';
}
return;
}
```
### 7. Delete `showMiniBranchMenu()` method entirely
Because the branches are now rendered directly inside `renderRecordUI()`, you must delete the entire `showMiniBranchMenu` function from the `Ejcees` class block.
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 08:48
由 ejsoon
改進:
當點擊#val-comment-h或它前面的span時,將彈出一個輸入框,輸入一個數字後,將其四捨五入至10的倍數,如果這個數字大於當前的#mini-comment-h-slider的max屬性,將把它的max和value都改為這個數值。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
代码: 全选
Here are the specific sections of the code that need to be modified to implement the prompt feature for the Max Height setting.
### 1. Update `renderDOM()`
Add an `id` and `cursor: pointer` to the label span so it is easily selectable and indicates to the user that it is clickable.
```javascript
// In renderDOM(), within the `if (this.mode === 'mini') { ... }` block
// Replace the first .mini-settings-row for Max Height with the following:
<div class="mini-settings-row">
<span class="mini-settings-label" id="label-comment-h" style="cursor: pointer;" title="Click to input specific height">Max Height:</span>
<span class="mini-settings-value" id="val-comment-h" style="cursor: pointer;" title="Click to input specific height">${this.miniCommentMaxH}</span>
<input type="range" id="mini-comment-h-slider" min="60" max="360" step="10" value="${this.miniCommentMaxH}">
</div>
```
### 2. Update `bindEvents()`
Locate the existing `#mini-comment-h-slider` logic and add the new click event listeners for the label and value spans.
```javascript
// In bindEvents(), within the `if (this.mode === 'mini') { ... }` block
// Replace the existing `commentHSlider` binding section with this:
const commentHSlider = this.container.querySelector('#mini-comment-h-slider');
const commentHVal = this.container.querySelector('#val-comment-h');
const commentHLabel = this.container.querySelector('#label-comment-h');
if (commentHSlider && commentHVal) {
// Original slider drag event
commentHSlider.addEventListener('input', e => {
this.miniCommentMaxH = e.target.value;
commentHVal.textContent = this.miniCommentMaxH;
innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
});
}
// Handler for prompt input when clicking the label or value
const handleCommentHeightInput = (e) => {
e.stopPropagation();
const userInput = prompt("Enter new max height (px):", this.miniCommentMaxH);
if (userInput !== null && userInput.trim() !== '') {
let parsedVal = parseInt(userInput, 10);
if (!isNaN(parsedVal)) {
// Round to the nearest multiple of 10
parsedVal = Math.round(parsedVal / 10) * 10;
const currentMax = parseInt(commentHSlider.max, 10);
// If the entered value is greater than the current max attribute, update max
if (parsedVal > currentMax) {
commentHSlider.max = parsedVal;
}
// Update the slider value, internal state, text content, and CSS variable
commentHSlider.value = parsedVal;
this.miniCommentMaxH = parsedVal;
commentHVal.textContent = this.miniCommentMaxH;
innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
}
}
};
// Bind the click events
if (commentHLabel) commentHLabel.addEventListener('click', handleCommentHeightInput);
if (commentHVal) commentHVal.addEventListener('click', handleCommentHeightInput);
```
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 09:04
由 ejsoon
把這個點擊方法擴展到全部四個項目的label和val中,它們輸入後,把這個值四捨五入為step的倍數。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
代码: 全选
改進:
這段代碼:
const handleCommentHeightInput = e => {
e.stopPropagation();
const userInput = prompt('Enter new max height (px):', this.miniCommentMaxH);
if (userInput !== null && userInput.trim() !== '') {
let parsedVal = parseInt(userInput, 10);
if (!isNaN(parsedVal)) {
// Round to the nearest multiple of 10
parsedVal = Math.round(parsedVal / 10) * 10;
const currentMax = parseInt(commentHSlider.max, 10);
// If the entered value is greater than the current max attribute, update max
if (parsedVal > currentMax) {
commentHSlider.max = parsedVal;
}
// Update the slider value, internal state, text content, and CSS variable
commentHSlider.value = parsedVal;
this.miniCommentMaxH = parsedVal;
commentHVal.textContent = this.miniCommentMaxH;
innerWrapper.style.setProperty('--mini-comment-max-h', this.miniCommentMaxH + 'px');
}
}
};
// Bind the click events
if (commentHLabel) commentHLabel.addEventListener('click', handleCommentHeightInput);
if (commentHVal) commentHVal.addEventListener('click', handleCommentHeightInput);
意思是當點commentHLabel和commentHVal時會彈出一個輸入框。
現在把這個點擊方法擴展到全部四個項目的label和val中,它們輸入後,把這個值四捨五入為step的倍數。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 09:07
由 ejsoon
gemini pro剛吃飽又餓了:
代码: 全选
Here are the specific sections of the code that need to be modified to extend the click-to-prompt functionality to all four settings and round the input to the nearest `step`.
### 1. Update `renderDOM()`
Add IDs, `cursor: pointer`, and `title` attributes to the label and value spans for all four settings to make them interactive.
```javascript
// In renderDOM(), within the `if (this.mode === 'mini') { ... }` block
// Replace the entire #mini-settings-panel div content with the following:
<div id="mini-settings-panel" class="mini-settings-panel" style="display: none;">
<div class="mini-settings-row">
<span class="mini-settings-label" id="label-comment-h" style="cursor: pointer;" title="Click to input specific value">Max Height:</span>
<span class="mini-settings-value" id="val-comment-h" style="cursor: pointer;" title="Click to input specific value">${this.miniCommentMaxH}</span>
<input type="range" id="mini-comment-h-slider" min="60" max="360" step="10" value="${this.miniCommentMaxH}">
</div>
<div class="mini-settings-row">
<span class="mini-settings-label" id="label-fz" style="cursor: pointer;" title="Click to input specific value">Font Size (px):</span>
<span class="mini-settings-value" id="val-fz" style="cursor: pointer;" title="Click to input specific value">${this.miniFontSize}</span>
<input type="range" id="mini-fz-slider" min="12" max="24" step="1" value="${this.miniFontSize}">
</div>
<div class="mini-settings-row">
<span class="mini-settings-label" id="label-interval" style="cursor: pointer;" title="Click to input specific value">Interval (s):</span>
<span class="mini-settings-value" id="val-interval" style="cursor: pointer;" title="Click to input specific value">${this.moveInterval}</span>
<input type="range" id="mini-interval-slider" min="1" max="4" step="0.2" value="${this.moveInterval}">
</div>
<div class="mini-settings-row">
<span class="mini-settings-label" id="label-speed" style="cursor: pointer;" title="Click to input specific value">Speed (s):</span>
<span class="mini-settings-value" id="val-speed" style="cursor: pointer;" title="Click to input specific value">${this.moveSpeed}</span>
<input type="range" id="mini-speed-slider" min="0.1" max="2" step="0.1" value="${this.moveSpeed}">
</div>
<div style="text-align: center;">
<button type="button" class="mini-exp-txt" id="mini-exp-btn">Export Text</button>
</div>
</div>
```
### 2. Update `bindEvents()`
Replace the scattered individual slider bindings with a unified configuration array that handles dragging, click-to-prompt, rounding to the `step` multiple, and bound updates for all four settings dynamically.
```javascript
// In bindEvents(), within the `if (this.mode === 'mini') { ... }` block
// Locate and completely REMOVE the individual event listener blocks for:
// commentHSlider, fzSlider, intervalSlider, speedSlider and handleCommentHeightInput.
// REPLACE them with this unified configuration block:
// Configuration for the four adjustable settings
const settingsConfig = [
{ id: 'comment-h', stateKey: 'miniCommentMaxH', cssVar: '--mini-comment-max-h', suffix: 'px' },
{ id: 'fz', stateKey: 'miniFontSize', cssVar: '--mini-fz', suffix: 'px' },
{ id: 'interval', stateKey: 'moveInterval', cssVar: null, suffix: '' },
{ id: 'speed', stateKey: 'moveSpeed', cssVar: null, suffix: '' }
];
settingsConfig.forEach(config => {
const slider = this.container.querySelector(`#mini-${config.id}-slider`);
const valSpan = this.container.querySelector(`#val-${config.id}`);
const label = this.container.querySelector(`#label-${config.id}`);
if (!slider || !valSpan) return;
// 1. Standard drag slider event
slider.addEventListener('input', e => {
const val = parseFloat(e.target.value);
this[config.stateKey] = val;
valSpan.textContent = val;
if (config.cssVar) {
innerWrapper.style.setProperty(config.cssVar, val + config.suffix);
}
});
// 2. Click-to-prompt handler for both Label and Value
const promptHandler = (e) => {
e.stopPropagation();
const step = parseFloat(slider.step) || 1;
const currentVal = this[config.stateKey];
const userInput = prompt(`Enter new value (step: ${step}):`, currentVal);
if (userInput !== null && userInput.trim() !== '') {
let parsedVal = parseFloat(userInput);
if (!isNaN(parsedVal)) {
// Round to the nearest multiple of the slider's step
// (toFixed(5) is used to avoid JS floating-point precision issues)
parsedVal = Number((Math.round(parsedVal / step) * step).toFixed(5));
// Dynamically adjust slider max/min boundaries if exceeded
const currentMax = parseFloat(slider.max);
const currentMin = parseFloat(slider.min);
if (parsedVal > currentMax) slider.max = parsedVal;
if (parsedVal < currentMin) slider.min = parsedVal;
// Apply the rounded value
slider.value = parsedVal;
this[config.stateKey] = parsedVal;
valSpan.textContent = parsedVal;
// Update CSS variables if mapped
if (config.cssVar) {
innerWrapper.style.setProperty(config.cssVar, parsedVal + config.suffix);
}
}
}
};
if (label) label.addEventListener('click', promptHandler);
if (valSpan) valSpan.addEventListener('click', promptHandler);
});
```
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 09:16
由 ejsoon
在調整mini-speed-slider的值時,它的值必須始終小於mini-interval-slider的值。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
代码: 全选
Here is the specific section of the code that needs to be modified. This update adds a constraint check to both the slider drag event and the prompt input event to ensure `moveSpeed` is always strictly less than `moveInterval`. It also reciprocally prevents `moveInterval` from being set equal to or lower than `moveSpeed`.
### 1. Update `bindEvents()`
Replace the `settingsConfig.forEach` loop inside the `if (this.mode === 'mini') { ... }` block with this updated version:
```javascript
// In bindEvents(), within the `if (this.mode === 'mini') { ... }` block
// Replace the entire settingsConfig.forEach block with the following:
settingsConfig.forEach(config => {
const slider = this.container.querySelector(`#mini-${config.id}-slider`);
const valSpan = this.container.querySelector(`#val-${config.id}`);
const label = this.container.querySelector(`#label-${config.id}`);
if (!slider || !valSpan) return;
// 1. Standard drag slider event
slider.addEventListener('input', e => {
let val = parseFloat(e.target.value);
const step = parseFloat(slider.step) || 1;
// Apply constraint: speed must always be strictly less than interval
if (config.id === 'speed' && val >= this.moveInterval) {
val = Math.max(parseFloat(slider.min), Number((this.moveInterval - step).toFixed(5)));
slider.value = val; // Force visual slider thumb back
} else if (config.id === 'interval' && val <= this.moveSpeed) {
val = Math.min(parseFloat(slider.max), Number((this.moveSpeed + step).toFixed(5)));
slider.value = val; // Force visual slider thumb back
}
this[config.stateKey] = val;
valSpan.textContent = val;
if (config.cssVar) {
innerWrapper.style.setProperty(config.cssVar, val + config.suffix);
}
});
// 2. Click-to-prompt handler for both Label and Value
const promptHandler = (e) => {
e.stopPropagation();
const step = parseFloat(slider.step) || 1;
const currentVal = this[config.stateKey];
const userInput = prompt(`Enter new value (step: ${step}):`, currentVal);
if (userInput !== null && userInput.trim() !== '') {
let parsedVal = parseFloat(userInput);
if (!isNaN(parsedVal)) {
// Round to the nearest multiple of the slider's step
// (toFixed(5) is used to avoid JS floating-point precision issues)
parsedVal = Number((Math.round(parsedVal / step) * step).toFixed(5));
// Apply constraint: speed must always be strictly less than interval
if (config.id === 'speed' && parsedVal >= this.moveInterval) {
parsedVal = Math.max(parseFloat(slider.min), Number((this.moveInterval - step).toFixed(5)));
} else if (config.id === 'interval' && parsedVal <= this.moveSpeed) {
parsedVal = Math.min(parseFloat(slider.max), Number((this.moveSpeed + step).toFixed(5)));
}
// Dynamically adjust slider max/min boundaries if exceeded
const currentMax = parseFloat(slider.max);
const currentMin = parseFloat(slider.min);
if (parsedVal > currentMax) slider.max = parsedVal;
if (parsedVal < currentMin) slider.min = parsedVal;
// Apply the rounded and constrained value
slider.value = parsedVal;
this[config.stateKey] = parsedVal;
valSpan.textContent = parsedVal;
// Update CSS variables if mapped
if (config.cssVar) {
innerWrapper.style.setProperty(config.cssVar, parsedVal + config.suffix);
}
}
}
};
if (label) label.addEventListener('click', promptHandler);
if (valSpan) valSpan.addEventListener('click', promptHandler);
});
```
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 09:20
由 ejsoon
ejceesobj202604290919
更改了mini的顯示,不再把分支和評論鋪到棋盤上,而是顯示在下方。
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 11:49
由 ejsoon
ejceesobj202604291142
解決PGN屬性設置窗口不能打開的問題,為output加上字體大小,mini數據不保存到localStorage。
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 11:54
由 ejsoon
Re: 將製作ejcees(中國象棋打譜程式)
发表于 : 2026年 4月 29日 12:21
由 ejsoon
導入有問題:
代码: 全选
[Game "Chinese Chess"]
[Title "河南楚河汉界弈强队 汪洋 胜 北京威凯&金环建设京冀联队 蒋川"]
[Event "2023年腾讯棋牌天天象棋全国象棋甲级联赛"]
[Red "汪洋"]
[RedTeam "河南楚河汉界弈强队"]
[Black "蒋川"]
[BlackName "北京威凯&金环建设京冀联队"]
[Opening "仙人指路对卒底炮"]
[Date ""]
[Site "河北遵化"]
[Round "第03轮"]
[Format "ICCS"]
1. C3-C4 B7-C7
2. B0-A2 H7-E7
3. H0-G2 H9-G7
4. A3-A4 A6-A5
5. A2-B4 A5-A4
6. B4-C6 E7-E8
7. B2-C2 E8-C8
8. I0-H0 C9-E7
9. A0-B0 I9-I8
10. G3-G4 C8-C6
11. C2-C6 B9-D8
12. C6-G6 A9-B9
13. B0-B9 C7-C0
14. D0-E1 D8-B9
15. H2-H8 A4-B4
16. G4-G5 B4-C4
17. G6-F6 G7-E8
18. H0-H7 C0-B0
19. G5-G6 B0-B7
20. H7-H5 E8-C7
21. G2-H4 D9-E8
22. G6-G7 I6-I5
23. H4-F5 C7-D5
24. F6-F8 I8-I6
25. F5-H6 B9-C7
26. F8-F2 C4-D4
27. F2-D2 B7-B5
28. H6-G8 E9-D9
29. H5-H4 B5-B8
30. D2-D5 B8-G8
31. G7-G8 C7-D5
32. H4-D4 I6-G6
33. D4-D5 D9-E9
34. G8-F8 G6-F6
35. F8-G8 F6-F3
36. D5-I5 F3-E3
37. H8-H9 E3-H3
38. H9-I9 H3-H9
39. I5-I8 *