更新包+readme

This commit is contained in:
2026-05-16 23:03:34 +08:00
parent a97dbee294
commit f2fd225079
4 changed files with 261 additions and 29 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ plugins {
}
group = "com.huangzj"
version = "0.1.0"
version = "1.0.0"
repositories {
// 国内镜像,失败再走中央仓库
@@ -1,8 +1,13 @@
package com.huangzj.protoplugin.memo;
import java.text.SimpleDateFormat;
import java.util.Date;
/** 备忘录条目(字段需可被持久化组件序列化)。 */
public class MemoItem {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public String id = "";
public String title = "";
public String content = "";
@@ -23,4 +28,24 @@ public class MemoItem {
}
return "(无标题)";
}
public String displayDate() {
if (updated <= 0) {
return "";
}
return DATE_FORMAT.format(new Date(updated));
}
public String contentPreview() {
if (content == null || content.trim().isEmpty()) {
return "";
}
String trimmed = content.trim();
int newlineIdx = trimmed.indexOf('\n');
String firstLine = newlineIdx >= 0 ? trimmed.substring(0, newlineIdx) : trimmed;
if (firstLine.length() > 50) {
return firstLine.substring(0, 50) + "...";
}
return firstLine;
}
}
@@ -1,6 +1,7 @@
package com.huangzj.protoplugin.memo;
import com.intellij.openapi.project.Project;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTextArea;
@@ -13,13 +14,16 @@ import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
@@ -51,17 +55,25 @@ public final class MemoPanel extends JPanel {
private final JPanel leftButtonBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
private final JPanel themeChooserBar = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0));
private final JPanel topStack = new JPanel(new BorderLayout());
private final JSplitPane split;
private final JBScrollPane listScroll;
private final JBScrollPane contentScroll;
private JSplitPane split;
private JBScrollPane listScroll;
private JBScrollPane contentScroll;
private final JPanel titleBlock = new JPanel(new BorderLayout(4, 4));
private final JPanel contentBlock = new JPanel(new BorderLayout(4, 4));
private final JPanel right = new JPanel(new BorderLayout(8, 8));
private final JBTextField searchField = new JBTextField();
private final JLabel countLabel = new JLabel(" ");
private final JLabel modifiedLabel = new JLabel(" ");
private final JPanel leftPanel = new JPanel(new BorderLayout(0, 4));
private final JPanel searchPanel = new JPanel(new BorderLayout(4, 0));
private final JLabel searchIcon = new JLabel("\uD83D\uDD0D");
private final JPanel editorStatusBar = new JPanel(new BorderLayout());
private boolean loadingSelection;
private boolean themeComboSuppress;
private MemoUiTheme currentTheme = MemoUiTheme.IDE_DEFAULT;
private final Font defaultLabelFont;
private List<MemoItem> allItems = new ArrayList<>();
public MemoPanel(@NotNull Project project) {
super(new BorderLayout(8, 8));
@@ -69,7 +81,25 @@ public final class MemoPanel extends JPanel {
defaultLabelFont = titleLabel.getFont();
setBorder(JBUI.Borders.empty(8));
initToolbar();
initFlair();
initTopStack();
initSearchPanel();
initList();
initLeftPanel();
initEditor();
initSplit();
initTheme();
initListeners();
reloadListModel();
}
private void initToolbar() {
leftButtonBar.setOpaque(false);
newBtn.setToolTipText("新建一条备忘录");
saveBtn.setToolTipText("保存当前备忘录");
deleteBtn.setToolTipText("删除选中的备忘录");
leftButtonBar.add(newBtn);
leftButtonBar.add(saveBtn);
leftButtonBar.add(deleteBtn);
@@ -81,21 +111,48 @@ public final class MemoPanel extends JPanel {
toolbar.setOpaque(false);
toolbar.add(leftButtonBar, BorderLayout.WEST);
toolbar.add(themeChooserBar, BorderLayout.EAST);
}
private void initFlair() {
flairLabel.setOpaque(true);
flairLabel.setFont(flairLabel.getFont().deriveFont(Font.PLAIN, 12f));
}
private void initTopStack() {
topStack.setOpaque(false);
topStack.add(flairLabel, BorderLayout.NORTH);
topStack.add(toolbar, BorderLayout.CENTER);
add(topStack, BorderLayout.NORTH);
}
private void initSearchPanel() {
searchField.getEmptyText().setText("搜索备忘录...");
searchField.setToolTipText("输入关键字过滤备忘录标题和内容");
searchPanel.setOpaque(false);
searchPanel.add(searchIcon, BorderLayout.WEST);
searchPanel.add(searchField, BorderLayout.CENTER);
searchPanel.setBorder(JBUI.Borders.empty(0, 0, 4, 0));
}
private void initList() {
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
installListRenderer();
listScroll = new JBScrollPane(list);
listScroll.setPreferredSize(new Dimension(200, 300));
countLabel.setFont(countLabel.getFont().deriveFont(Font.PLAIN, 11f));
countLabel.setHorizontalAlignment(SwingConstants.RIGHT);
countLabel.setBorder(JBUI.Borders.empty(2, 4, 0, 4));
}
private void initLeftPanel() {
leftPanel.setOpaque(false);
leftPanel.add(searchPanel, BorderLayout.NORTH);
leftPanel.add(listScroll, BorderLayout.CENTER);
leftPanel.add(countLabel, BorderLayout.SOUTH);
}
private void initEditor() {
contentArea.setLineWrap(true);
contentArea.setWrapStyleWord(true);
contentScroll = new JBScrollPane(contentArea);
@@ -108,22 +165,34 @@ public final class MemoPanel extends JPanel {
contentBlock.add(contentLabel, BorderLayout.NORTH);
contentBlock.add(contentScroll, BorderLayout.CENTER);
modifiedLabel.setFont(modifiedLabel.getFont().deriveFont(Font.PLAIN, 11f));
modifiedLabel.setBorder(JBUI.Borders.empty(2, 0, 0, 0));
editorStatusBar.setOpaque(false);
editorStatusBar.add(modifiedLabel, BorderLayout.EAST);
right.setOpaque(false);
right.add(titleBlock, BorderLayout.NORTH);
right.add(contentBlock, BorderLayout.CENTER);
right.add(editorStatusBar, BorderLayout.SOUTH);
titleField.setPreferredSize(new Dimension(200, 28));
}
split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, listScroll, right);
private void initSplit() {
split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, right);
split.setResizeWeight(0.28);
split.setOpaque(false);
add(split, BorderLayout.CENTER);
}
private void initTheme() {
MemoUiTheme initial = MemoProjectService.getInstance(project).getMemoTheme();
themeComboSuppress = true;
themeCombo.setSelectedItem(initial);
themeComboSuppress = false;
applyTheme(initial);
}
private void initListeners() {
themeCombo.addActionListener(
e -> {
if (themeComboSuppress) {
@@ -136,8 +205,6 @@ public final class MemoPanel extends JPanel {
}
});
reloadListModel();
list.addListSelectionListener(
new ListSelectionListener() {
@Override
@@ -172,6 +239,26 @@ public final class MemoPanel extends JPanel {
refreshSelectedTitleInList();
}
});
searchField
.getDocument()
.addDocumentListener(
new javax.swing.event.DocumentListener() {
@Override
public void insertUpdate(javax.swing.event.DocumentEvent e) {
filterList();
}
@Override
public void removeUpdate(javax.swing.event.DocumentEvent e) {
filterList();
}
@Override
public void changedUpdate(javax.swing.event.DocumentEvent e) {
filterList();
}
});
}
private void installListRenderer() {
@@ -184,27 +271,57 @@ public final class MemoPanel extends JPanel {
int index,
boolean isSelected,
boolean cellHasFocus) {
super.getListCellRendererComponent(
JLabel label = (JLabel) super.getListCellRendererComponent(
jList, value, index, isSelected, cellHasFocus);
if (value instanceof MemoItem) {
setText(((MemoItem) value).displayTitle());
MemoItem item = (MemoItem) value;
String title = escapeHtml(item.displayTitle());
String date = item.displayDate();
String preview = item.contentPreview();
StringBuilder html = new StringBuilder(128);
html.append("<html><body style='width:160px'>");
html.append("<b>").append(title).append("</b>");
if (!date.isEmpty()) {
html.append("<br><span style='font-size:10px;color:")
.append(isSelected ? "#inherit" : "#888888")
.append("'>").append(date).append("</span>");
}
if (!preview.isEmpty()) {
html.append("<br><span style='font-size:10px;color:")
.append(isSelected ? "#inherit" : "#AAAAAA")
.append("'>").append(escapeHtml(preview)).append("</span>");
}
html.append("</body></html>");
label.setText(html.toString());
}
if (currentTheme != MemoUiTheme.IDE_DEFAULT) {
MemoThemeSpec spec = MemoThemeSpec.forTheme(currentTheme);
setOpaque(true);
label.setOpaque(true);
if (isSelected) {
setBackground(spec.listSelectedBg);
setForeground(spec.listSelectedFg);
label.setBackground(spec.listSelectedBg);
label.setForeground(spec.listSelectedFg);
} else {
setBackground(spec.listBg);
setForeground(spec.text);
label.setBackground(spec.listBg);
label.setForeground(spec.text);
}
}
return this;
label.setBorder(JBUI.Borders.empty(4, 8, 4, 8));
return label;
}
});
}
private static String escapeHtml(String text) {
if (text == null) {
return "";
}
return text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;");
}
private void applyTheme(@NotNull MemoUiTheme theme) {
currentTheme = theme;
installListRenderer();
@@ -235,6 +352,9 @@ public final class MemoPanel extends JPanel {
flairLabel.setBackground(panelBg);
flairLabel.setForeground(spec.text);
split.setBackground(panelBg);
leftPanel.setBackground(panelBg);
searchPanel.setBackground(panelBg);
searchIcon.setForeground(spec.text);
listScroll.setBackground(panelBg);
listScroll.getViewport().setBackground(spec.listBg);
list.setBackground(spec.listBg);
@@ -244,12 +364,15 @@ public final class MemoPanel extends JPanel {
contentBlock.setBackground(panelBg);
titleLabel.setForeground(spec.text);
contentLabel.setForeground(spec.text);
countLabel.setForeground(spec.text);
modifiedLabel.setForeground(spec.text);
editorStatusBar.setBackground(panelBg);
if (theme == MemoUiTheme.CUTE_CANDY) {
flairLabel.setVisible(true);
flairLabel.setText("✨ 今天也要甜甜哒~ 选一条备忘,把想法留下来吧 💕");
titleLabel.setText("📝 标题");
contentLabel.setText("💗 正文");
flairLabel.setText("\u2728 今天也要甜甜哒\uFF5E 选一条备忘,把想法留下来吧 \uD83D\uDC95");
titleLabel.setText("\uD83D\uDCDD 标题");
contentLabel.setText("\uD83D\uDC97 正文");
titleLabel.setFont(defaultLabelFont.deriveFont(Font.BOLD, defaultLabelFont.getSize() + 1f));
contentLabel.setFont(
defaultLabelFont.deriveFont(Font.BOLD, defaultLabelFont.getSize() + 1f));
@@ -263,6 +386,7 @@ public final class MemoPanel extends JPanel {
}
styleTextField(titleField, spec, softField);
styleTextField(searchField, spec, softField);
styleTextArea(contentArea, contentScroll, spec, softField);
styleAccentButton(newBtn, spec);
@@ -281,6 +405,7 @@ public final class MemoPanel extends JPanel {
Color editorBg = UIUtil.getTextFieldBackground();
Color fg = UIUtil.getTextFieldForeground();
Color listBg = UIUtil.getListBackground();
Color labelFg = UIUtil.getLabelForeground();
setBackground(panelBg);
topStack.setOpaque(false);
@@ -289,12 +414,15 @@ public final class MemoPanel extends JPanel {
toolbar.setOpaque(false);
leftButtonBar.setOpaque(false);
themeChooserBar.setOpaque(false);
themeChooserLabel.setForeground(UIUtil.getLabelForeground());
themeChooserLabel.setForeground(labelFg);
flairLabel.setVisible(false);
flairLabel.setText(" ");
flairLabel.setBackground(panelBg);
flairLabel.setForeground(UIUtil.getLabelForeground());
flairLabel.setForeground(labelFg);
split.setBackground(panelBg);
leftPanel.setBackground(panelBg);
searchPanel.setBackground(panelBg);
searchIcon.setForeground(labelFg);
listScroll.setBackground(panelBg);
listScroll.getViewport().setBackground(listBg);
list.setBackground(listBg);
@@ -306,14 +434,19 @@ public final class MemoPanel extends JPanel {
contentLabel.setText("内容");
titleLabel.setFont(defaultLabelFont);
contentLabel.setFont(defaultLabelFont);
titleLabel.setForeground(UIUtil.getLabelForeground());
contentLabel.setForeground(UIUtil.getLabelForeground());
titleLabel.setForeground(labelFg);
contentLabel.setForeground(labelFg);
countLabel.setForeground(JBColor.GRAY);
modifiedLabel.setForeground(JBColor.GRAY);
editorStatusBar.setBackground(panelBg);
resetFieldLook(titleField, editorBg, fg);
resetFieldLook(searchField, editorBg, fg);
resetFieldLook(contentArea, editorBg, fg);
contentScroll.getViewport().setBackground(editorBg);
contentArea.setCaretColor(fg);
titleField.setCaretColor(fg);
searchField.setCaretColor(fg);
resetButton(newBtn);
resetButton(saveBtn);
@@ -392,19 +525,68 @@ public final class MemoPanel extends JPanel {
}
private void reloadListModel() {
allItems = MemoProjectService.getInstance(project).getItems();
filterList();
}
private void filterList() {
String query = searchField.getText().trim().toLowerCase();
listModel.clear();
List<MemoItem> items = MemoProjectService.getInstance(project).getItems();
for (MemoItem m : items) {
List<MemoItem> filtered;
if (query.isEmpty()) {
filtered = allItems;
} else {
filtered = allItems.stream()
.filter(m -> matchesQuery(m, query))
.collect(Collectors.toList());
}
for (MemoItem m : filtered) {
listModel.addElement(m);
}
if (!items.isEmpty() && list.getSelectedIndex() < 0) {
updateCountLabel(filtered.size());
if (!filtered.isEmpty() && list.getSelectedIndex() < 0) {
list.setSelectedIndex(0);
}
if (filtered.isEmpty() && !allItems.isEmpty() && !query.isEmpty()) {
clearEditor();
modifiedLabel.setText("无匹配结果");
} else if (filtered.isEmpty() && allItems.isEmpty()) {
clearEditor();
modifiedLabel.setText(" ");
}
}
private static boolean matchesQuery(MemoItem m, String query) {
if (m.title != null && m.title.toLowerCase().contains(query)) {
return true;
}
if (m.content != null && m.content.toLowerCase().contains(query)) {
return true;
}
return false;
}
private void updateCountLabel(int visibleCount) {
int total = allItems.size();
if (total == 0) {
countLabel.setText(" ");
} else if (visibleCount == total) {
countLabel.setText("" + total + "");
} else {
countLabel.setText("显示 " + visibleCount + " / " + total + "");
}
}
private void createMemo() {
MemoItem m = MemoProjectService.getInstance(project).createMemo();
listModel.addElement(m);
allItems = MemoProjectService.getInstance(project).getItems();
searchField.setText("");
filterList();
list.setSelectedValue(m, true);
titleField.requestFocusInWindow();
}
@@ -417,7 +599,9 @@ public final class MemoPanel extends JPanel {
m.title = titleField.getText();
m.content = contentArea.getText();
m.updated = System.currentTimeMillis();
allItems = MemoProjectService.getInstance(project).getItems();
list.repaint();
updateModifiedLabel(m);
}
private void deleteCurrent() {
@@ -425,9 +609,21 @@ public final class MemoPanel extends JPanel {
if (m == null) {
return;
}
String itemTitle = m.displayTitle();
int result = JOptionPane.showConfirmDialog(
this,
"确定要删除「" + itemTitle + "」吗?\n此操作不可撤销。",
"确认删除",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result != JOptionPane.YES_OPTION) {
return;
}
int idx = list.getSelectedIndex();
MemoProjectService.getInstance(project).removeById(m.id);
allItems = MemoProjectService.getInstance(project).getItems();
listModel.removeElement(m);
updateCountLabel(listModel.getSize());
if (!listModel.isEmpty()) {
int next = Math.min(idx, listModel.getSize() - 1);
list.setSelectedIndex(Math.max(0, next));
@@ -445,15 +641,26 @@ public final class MemoPanel extends JPanel {
} else {
titleField.setText(m.title != null ? m.title : "");
contentArea.setText(m.content != null ? m.content : "");
updateModifiedLabel(m);
}
} finally {
loadingSelection = false;
}
}
private void updateModifiedLabel(MemoItem m) {
String date = m.displayDate();
if (!date.isEmpty()) {
modifiedLabel.setText("最后修改: " + date);
} else {
modifiedLabel.setText(" ");
}
}
private void clearEditor() {
titleField.setText("");
contentArea.setText("");
modifiedLabel.setText(" ");
}
private void refreshSelectedTitleInList() {
@@ -462,4 +669,4 @@ public final class MemoPanel extends JPanel {
}
javax.swing.SwingUtilities.invokeLater(() -> list.repaint());
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
<idea-plugin>
<id>com.huangzj.protoPlugin</id>
<name>Idea-Plugin</name>
<name>Huangzhijun-Idea-Plugin</name>
<vendor email="support@example.com" url="https://example.com">huangzj</vendor>
<description><![CDATA[