diff --git a/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.cpp b/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.cpp index 66cb9114c9..325b980c0b 100644 --- a/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.cpp +++ b/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.cpp @@ -1132,6 +1132,10 @@ static const ossia::string_map& root_parse{[] { if(v.get_type() == sajson::TYPE_STRING) d.credits = v.as_string(); }}); + p.insert({"DOCUMENTATION", [](descriptor& d, const sajson::value& v) { + if(v.get_type() == sajson::TYPE_STRING) + d.documentation = v.as_string(); + }}); p.insert({"CATEGORIES", [](descriptor& d, const sajson::value& v) { if(v.get_type() == sajson::TYPE_ARRAY) { @@ -2914,6 +2918,12 @@ std::string parser::write_isf() const oss << " \"CREDIT\": \"" << escape_json(m_desc.credits) << "\",\n"; } + // Add documentation URL if present + if(!m_desc.documentation.empty()) + { + oss << " \"DOCUMENTATION\": \"" << escape_json(m_desc.documentation) << "\",\n"; + } + // Add categories if present if(!m_desc.categories.empty()) { diff --git a/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.hpp b/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.hpp index dd0ff5f4ec..5b144d1110 100644 --- a/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.hpp +++ b/src/plugins/score-plugin-gfx/3rdparty/libisf/src/isf.hpp @@ -332,6 +332,7 @@ struct descriptor } mode{ISF}; std::string description; std::string credits; + std::string documentation; std::vector categories; std::vector inputs; std::vector outputs; // Parsed from OUTPUTS array; empty = single color output diff --git a/src/plugins/score-plugin-gfx/Gfx/ISFProcess.hpp b/src/plugins/score-plugin-gfx/Gfx/ISFProcess.hpp index d0f9b4cee6..4acb643080 100644 --- a/src/plugins/score-plugin-gfx/Gfx/ISFProcess.hpp +++ b/src/plugins/score-plugin-gfx/Gfx/ISFProcess.hpp @@ -42,6 +42,8 @@ struct ISFHelpers base.author = QString::fromStdString(desc.credits); if(!desc.description.empty()) base.description = QString::fromStdString(desc.description); + if(!desc.documentation.empty()) + base.documentationLink = QUrl(QString::fromStdString(desc.documentation)); for(auto& cat : desc.categories) base.tags.push_back(QString::fromStdString(cat)); } diff --git a/src/plugins/score-plugin-js/JS/JSProcessFactory.hpp b/src/plugins/score-plugin-js/JS/JSProcessFactory.hpp index 6ee88a25b2..1f7f9a6168 100644 --- a/src/plugins/score-plugin-js/JS/JSProcessFactory.hpp +++ b/src/plugins/score-plugin-js/JS/JSProcessFactory.hpp @@ -16,7 +16,40 @@ struct LanguageSpec static constexpr const char* language = "JS"; }; -using ProcessFactory = Process::ProcessFactory_T; +// Scan the first lines of a QML script for a `// @documentation ` +// comment so a preset can point F1 to its own help page. +inline QUrl documentationUrlFromScript(const QString& script) noexcept +{ + static const QString tag = QStringLiteral("// @documentation"); + int line = 0; + for(const QString& raw : script.split('\n')) + { + if(++line > 20) + break; + const QString trimmed = raw.trimmed(); + if(trimmed.startsWith(tag)) + { + const QString url = trimmed.mid(tag.size()).trimmed(); + if(!url.isEmpty()) + return QUrl(url); + } + } + return {}; +} + +class ProcessFactory final : public Process::ProcessFactory_T +{ +public: + using Process::ProcessFactory_T::descriptor; + Process::Descriptor descriptor(const Process::ProcessModel& m) const noexcept override + { + auto desc = Metadata::get(); + const auto& js = safe_cast(m); + if(auto url = documentationUrlFromScript(js.executionScript()); !url.isEmpty()) + desc.documentationLink = url; + return desc; + } +}; struct LayerFactory : Process::EffectLayerFactory_Base { using Model_T = JS::ProcessModel;