ken changes
This commit is contained in:
parent
478e6e8ec9
commit
e6c7b932aa
|
@ -67,6 +67,32 @@ export class JsmindSearch {
|
|||
// Ensure input event is not bound multiple times
|
||||
inputField.removeEventListener('input', this.onInput)
|
||||
inputField.addEventListener('input', this.onInput.bind(this, node))
|
||||
|
||||
inputField.removeEventListener('keydown', this.onKeyDown)
|
||||
inputField.addEventListener('keydown', this.onKeyDown.bind(this, node))
|
||||
}
|
||||
/**
|
||||
* 處理 Enter 鍵完成輸入
|
||||
* Handle Enter key to finalize input
|
||||
* @param {Object} node - 當前節點
|
||||
* @param {KeyboardEvent} e - 鍵盤事件
|
||||
*/
|
||||
onKeyDown(node, e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
const input = e.target.value.trim()
|
||||
if (input) {
|
||||
// 更新節點文字
|
||||
node.data.text = input
|
||||
this.jm.end_edit()
|
||||
this.jm.update_node(node.id, input)
|
||||
|
||||
// 隱藏 suggestion box(避免未選建議但仍留下)
|
||||
if (this.suggestionBox) {
|
||||
this.suggestionBox.style.display = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,10 +132,22 @@ export class JsmindSearch {
|
|||
// 更新建議框內容
|
||||
// Update suggestion box content
|
||||
this.suggestionBox.innerHTML = results
|
||||
.map(
|
||||
(item) =>
|
||||
`<div class="${SUGGESTION_ITEM_CLASS}" data-link="${item.link}" data-text="${item.text}">${item.text}</div>`
|
||||
)
|
||||
.map(item => {
|
||||
const fieldHtml = item.fields.map(f => {
|
||||
const txt = f.url
|
||||
? `<a href="${f.url}" target="_blank">${f.text}</a>`
|
||||
: f.text;
|
||||
return `<div class="field-row"><strong>${f.title}:</strong> ${txt}</div>`;
|
||||
}).join("");
|
||||
|
||||
return `
|
||||
<div class="suggestion-item" data-link="${item.link}">
|
||||
${fieldHtml}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
|
||||
|
||||
.join('')
|
||||
|
||||
this.suggestionBox.style.left = `${left}px`
|
||||
|
@ -145,20 +183,72 @@ export class JsmindSearch {
|
|||
* @param {Object} node - 當前選中節點 (Selected node)
|
||||
* @param {Event} e - 點擊事件 (Click event)
|
||||
*/
|
||||
// onSuggestionClick(node, e) {
|
||||
// e.preventDefault()
|
||||
|
||||
// const text = e.target.getAttribute('data-text')
|
||||
// const link = e.target.getAttribute('data-link')
|
||||
|
||||
// node.data.text = text
|
||||
// node.data.link = link
|
||||
|
||||
// this.jm.end_edit()
|
||||
// this.jm.update_node(node.id, text)
|
||||
|
||||
// // 選擇後隱藏建議框
|
||||
// // Hide suggestions after selection
|
||||
// this.suggestionBox.style.display = 'none'
|
||||
// }
|
||||
onSuggestionClick(node, e) {
|
||||
e.preventDefault()
|
||||
|
||||
const text = e.target.getAttribute('data-text')
|
||||
const link = e.target.getAttribute('data-link')
|
||||
const item = e.currentTarget // 確保抓到整個 .suggestion-item DIV
|
||||
const html = item.innerHTML // 取得完整 HTML 當作 topic
|
||||
|
||||
node.data.text = text
|
||||
node.data.link = link
|
||||
node.data.text = html
|
||||
node.data.link = item.getAttribute('data-link')
|
||||
|
||||
this.jm.end_edit()
|
||||
this.jm.update_node(node.id, text)
|
||||
this.jm.update_node(node.id, html)
|
||||
|
||||
// 選擇後隱藏建議框
|
||||
// Hide suggestions after selection
|
||||
this.suggestionBox.style.display = 'none'
|
||||
}
|
||||
|
||||
}
|
||||
// ✅ 新增播放語音事件委派,支援動態插入的 voice-player
|
||||
let audio;
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
const target = e.target.closest('.voice-player');
|
||||
if (!target) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
let status = target.getAttribute('status');
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
}
|
||||
|
||||
if (status === 'playing') {
|
||||
target.setAttribute('status', '');
|
||||
const icon = target.querySelector('i');
|
||||
icon?.classList.remove('fa-pause');
|
||||
icon?.classList.add('fa-play');
|
||||
} else {
|
||||
let mp3_url = target.getAttribute('data-content');
|
||||
audio = new Audio(mp3_url);
|
||||
audio.play();
|
||||
|
||||
target.setAttribute('status', 'playing');
|
||||
const icon = target.querySelector('i');
|
||||
icon?.classList.remove('fa-play');
|
||||
icon?.classList.add('fa-pause');
|
||||
|
||||
audio.onended = function() {
|
||||
target.setAttribute('status', '');
|
||||
icon?.classList.remove('fa-pause');
|
||||
icon?.classList.add('fa-play');
|
||||
};
|
||||
}
|
||||
});
|
|
@ -79,7 +79,7 @@ jmnode {
|
|||
color: #333;
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 1px #666;
|
||||
font: 1.4em/1.125 Verdana, Arial, Helvetica, sans-serif;
|
||||
font: 1em/1.125 Verdana, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
jmnode:hover {
|
||||
|
@ -96,8 +96,7 @@ jmnode.selected {
|
|||
}
|
||||
|
||||
jmnode.root {
|
||||
font-size:1.6em;
|
||||
font-size:2em;
|
||||
font-size:1.2em;
|
||||
/* 展開/收合按鈕 */
|
||||
border-color: gray;
|
||||
}
|
||||
|
@ -113,12 +112,12 @@ jmexpander:hover {
|
|||
jmnode {
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 1.6em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
jmnode.root {
|
||||
/* font-size: 21px; */
|
||||
font-size: 1.8em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +152,7 @@ jmexpander:hover {
|
|||
============================ */
|
||||
.jsmind-suggestions {
|
||||
position: absolute;
|
||||
height: 200px;
|
||||
height: fit-content;
|
||||
width: 200px;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
|
@ -170,3 +169,24 @@ jmexpander:hover {
|
|||
.suggestion-item:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.field-row{
|
||||
.column_entry_files{
|
||||
margin-top: 1em;
|
||||
}
|
||||
a{
|
||||
color: unset;
|
||||
}
|
||||
strong{
|
||||
display: none;
|
||||
}
|
||||
&:nth-child(2){
|
||||
a{
|
||||
font-weight: 500;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
}
|
||||
&:nth-child(4){
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<div class="entry-suggestion">
|
||||
<strong><%= entry.column_entries.first.try(:text).to_s %></strong>
|
||||
|
||||
<% entry.column_entries.each do |ce| %>
|
||||
<% if ce.type == "file" %>
|
||||
<ul class="column_entry_files">
|
||||
<% ce.column_entry_files.desc(:sort_number).each do |file| %>
|
||||
<% next unless file.choose_lang_display(I18n.locale.to_s) %>
|
||||
<% file_title = file.get_file_title %>
|
||||
<% size = number_to_human_size(file.file.size) %>
|
||||
<% link = file.get_link %>
|
||||
<% if file.file.content_type.start_with?('audio/') %>
|
||||
<div class="voice-player">
|
||||
<span class="voice-title"><%= file_title %></span>
|
||||
<a class="voice-player" data-content="<%= file.file.url %>" href="#" title="<%= file_title %>">
|
||||
<i class="fa fa-play" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<li class="column_entry_file">
|
||||
<a class="column_entry_file_link" href="<%= link %>" title="<%= file_title %>" target="_blank">
|
||||
<%= file_title %>
|
||||
</a>
|
||||
<span class="file_size">(<%= size %>)</span>
|
||||
<span class="view_count">
|
||||
<i class="fa fa-eye" title="<%= t("universal_table.downloaded_times") %>"></i>
|
||||
<span class="view-count"><%= file.download_count %></span>
|
||||
</span>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -48,31 +48,53 @@ class Admin::UniversalTablesController < OrbitAdminController
|
|||
end
|
||||
end
|
||||
|
||||
def get_entries
|
||||
table = UTable.where(:uid => params["uid"]).first rescue nil
|
||||
data = []
|
||||
if !table.nil?
|
||||
if params[:q].present?
|
||||
enteries = search_data(table, 50)
|
||||
ma = ModuleApp.find_by_key("universal_table")
|
||||
enteries.each do |entry|
|
||||
if params["links"].present?
|
||||
data << {
|
||||
"id" => entry.id.to_s,
|
||||
"text" => entry.column_entries.first.text,
|
||||
"link" => OrbitHelper.cal_url_to_show(ma,entry)
|
||||
}
|
||||
else
|
||||
data << {
|
||||
"id" => entry.id.to_s,
|
||||
"text" => entry.column_entries.first.text
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
render :json => data.to_json
|
||||
end
|
||||
def get_entries
|
||||
table = UTable.where(:uid => params["uid"]).first rescue nil
|
||||
data = []
|
||||
|
||||
if table && params[:q].present?
|
||||
entries = search_data(table, 50)
|
||||
ma = ModuleApp.find_by_key("universal_table")
|
||||
|
||||
entries.each do |entry|
|
||||
rows = []
|
||||
entry.column_entries.each do |ce|
|
||||
ct = ce.table_column
|
||||
next if ct.nil?
|
||||
next if ct.display_in_index == false
|
||||
|
||||
text = ce.get_frontend_text(ct)
|
||||
next if text.blank?
|
||||
|
||||
# 包含連結的欄位處理
|
||||
url = ct.is_link_to_show ? OrbitHelper.cal_url_to_show(ma, entry) : nil
|
||||
rows << {
|
||||
"title" => ct.title,
|
||||
"text" => text,
|
||||
"url" => url
|
||||
}
|
||||
end
|
||||
|
||||
# 加入 hashtags 欄位
|
||||
# rows << {
|
||||
# "title" => I18n.t("universal_table.hashtags"),
|
||||
# "text" => entry.tags_for_frontend,
|
||||
# "url" => nil
|
||||
# }
|
||||
|
||||
# 加入主輸出結構
|
||||
data << {
|
||||
"id" => entry.id.to_s,
|
||||
"link" => OrbitHelper.cal_url_to_show(ma, entry),
|
||||
"text" => entry.column_entries.map(&:text).compact.reject(&:blank?).join(" / "),
|
||||
"fields" => rows
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
render json: data
|
||||
end
|
||||
|
||||
|
||||
def get_mindmaps
|
||||
utable = UTable.where(:uid => params['table']).first
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%
|
||||
data = action_data
|
||||
OrbitHelper.render_css_in_head(["/mind_map/mindmap"])
|
||||
OrbitHelper.render_css_in_head(["mind_map/mindmap"])
|
||||
%>
|
||||
|
||||
<h2><%= data["title"] %></h2>
|
||||
|
@ -31,6 +31,7 @@
|
|||
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||
const options = {
|
||||
container: 'jsmind_container',
|
||||
support_html: true,
|
||||
editable: isEditable,
|
||||
theme: 'primary',
|
||||
mode: 'full',
|
||||
|
|
Loading…
Reference in New Issue