#include "denote_fileitemaction.h" #include "denote_frontmatter.h" #include "denote_metadata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_CLASS_WITH_JSON(DenoteFileItemAction, "denote_fileitemaction.json") DenoteFileItemAction::DenoteFileItemAction(QObject *parent, const QVariantList &) : KAbstractFileItemActionPlugin(parent) { } QList DenoteFileItemAction::actions(const KFileItemListProperties &fileItemInfos, QWidget *parentWidget) { if (fileItemInfos.items().count() != 1) return {}; const KFileItem item = fileItemInfos.items().first(); // Directory: show "New Denote File..." if (item.isDir()) { auto *action = new QAction(QStringLiteral("New Denote File..."), parentWidget); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); const QString dirPath = item.localPath(); connect(action, &QAction::triggered, this, [this, dirPath, parentWidget]() { createDenoteFile(dirPath, parentWidget); }); return {action}; } // Non-denote file: show "Rename with Denote..." Denote::Metadata meta = Denote::parseFilename(item.localPath()); if (meta.isValid()) return {}; auto *action = new QAction(QStringLiteral("Rename with Denote..."), parentWidget); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); const KFileItemList items = fileItemInfos.items(); connect(action, &QAction::triggered, this, [this, items, parentWidget]() { renameWithDenote(items, parentWidget); }); return {action}; } void DenoteFileItemAction::renameWithDenote(const KFileItemList &items, QWidget *parentWidget) { if (items.isEmpty()) return; const KFileItem item = items.first(); const QString path = item.localPath(); const QFileInfo fi(path); // Pre-fill title from filename (without extension) QString defaultTitle = fi.completeBaseName(); defaultTitle.replace(QLatin1Char('-'), QLatin1Char(' ')); defaultTitle.replace(QLatin1Char('_'), QLatin1Char(' ')); // Try to extract title from frontmatter if applicable const QString ext = QStringLiteral(".") + fi.completeSuffix(); Denote::FileType fileType = Denote::fileTypeFromExtension(ext); if (fileType != Denote::FileType::Unknown) { QFile file(path); if (file.open(QIODevice::ReadOnly)) { QByteArray content = file.readAll(); file.close(); Denote::FrontMatter fm = Denote::parseFrontMatter(content, fileType); if (!fm.title.isEmpty()) defaultTitle = fm.title; } } // Build dialog QDialog dialog(parentWidget); dialog.setWindowTitle(QStringLiteral("Rename with Denote")); // Size to 33% of screen width (accounts for HiDPI) if (auto *screen = dialog.screen()) { dialog.setMinimumWidth(screen->availableGeometry().width() / 3); } auto *layout = new QVBoxLayout(&dialog); auto *form = new QFormLayout(); auto *titleEdit = new QLineEdit(defaultTitle); form->addRow(QStringLiteral("Title:"), titleEdit); auto *tagsEdit = new QLineEdit(); tagsEdit->setPlaceholderText(QStringLiteral("tag1, tag2, tag3")); form->addRow(QStringLiteral("Tags:"), tagsEdit); auto *sigEdit = new QLineEdit(); sigEdit->setPlaceholderText(QStringLiteral("(optional)")); form->addRow(QStringLiteral("Signature:"), sigEdit); layout->addLayout(form); auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); layout->addWidget(buttons); if (dialog.exec() != QDialog::Accepted) return; const QString title = titleEdit->text().trimmed(); if (title.isEmpty()) return; QStringList tags; const QString tagsText = tagsEdit->text().trimmed(); if (!tagsText.isEmpty()) { tags = tagsText.split(QLatin1Char(','), Qt::SkipEmptyParts); for (auto &t : tags) t = t.trimmed().toLower(); } const QString signature = sigEdit->text().trimmed(); // Build new filename const QString newName = Denote::buildFilename(title, tags, signature, ext); const QString newPath = fi.dir().filePath(newName); // Rename (use ::rename for atomic, no-copy behavior) ::rename(path.toLocal8Bit().constData(), newPath.toLocal8Bit().constData()); // Update frontmatter if supported if (fileType != Denote::FileType::Unknown) { QFile file(newPath); if (file.open(QIODevice::ReadWrite)) { QByteArray content = file.readAll(); Denote::FrontMatter fm; fm.title = title; fm.tags = tags; fm.identifier = Denote::parseFilename(newPath).identifier; fm.signature = signature; // Replace or prepend frontmatter QString body = Denote::extractBody(content, fileType); QByteArray newContent = Denote::marshalFrontMatter(fm, fileType); newContent += body.toUtf8(); file.seek(0); file.resize(0); file.write(newContent); file.close(); } } } void DenoteFileItemAction::createDenoteFile(const QString &dirPath, QWidget *parentWidget) { QDialog dialog(parentWidget); dialog.setWindowTitle(QStringLiteral("New Denote File")); if (auto *screen = dialog.screen()) { dialog.setMinimumWidth(screen->availableGeometry().width() / 3); } auto *layout = new QVBoxLayout(&dialog); auto *form = new QFormLayout(); auto *titleEdit = new QLineEdit(); titleEdit->setPlaceholderText(QStringLiteral("Note title")); form->addRow(QStringLiteral("Title:"), titleEdit); auto *tagsEdit = new QLineEdit(); tagsEdit->setPlaceholderText(QStringLiteral("tag1, tag2, tag3")); form->addRow(QStringLiteral("Tags:"), tagsEdit); auto *sigEdit = new QLineEdit(); sigEdit->setPlaceholderText(QStringLiteral("(optional)")); form->addRow(QStringLiteral("Signature:"), sigEdit); auto *extEdit = new QLineEdit(QStringLiteral(".org")); form->addRow(QStringLiteral("Extension:"), extEdit); layout->addLayout(form); auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); layout->addWidget(buttons); if (dialog.exec() != QDialog::Accepted) return; const QString title = titleEdit->text().trimmed(); if (title.isEmpty()) return; QStringList tags; const QString tagsText = tagsEdit->text().trimmed(); if (!tagsText.isEmpty()) { tags = tagsText.split(QLatin1Char(','), Qt::SkipEmptyParts); for (auto &t : tags) t = t.trimmed().toLower(); } const QString signature = sigEdit->text().trimmed(); QString ext = extEdit->text().trimmed(); if (!ext.startsWith(QLatin1Char('.'))) ext.prepend(QLatin1Char('.')); // Build filename and full path const QString filename = Denote::buildFilename(title, tags, signature, ext); const QString filePath = QDir(dirPath).filePath(filename); // Create file with frontmatter if supported Denote::FileType fileType = Denote::fileTypeFromExtension(ext); QFile file(filePath); if (file.open(QIODevice::WriteOnly)) { if (fileType != Denote::FileType::Unknown) { Denote::FrontMatter fm; fm.title = title; fm.tags = tags; fm.identifier = Denote::parseFilename(filePath).identifier; fm.signature = signature; file.write(Denote::marshalFrontMatter(fm, fileType)); } file.close(); } } #include "denote_fileitemaction.moc"